@assistkick/create 1.7.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/dist/bin/create.js +0 -0
  2. package/package.json +9 -7
  3. package/templates/assistkick-product-system/.env.example +1 -0
  4. package/templates/assistkick-product-system/local.db +0 -0
  5. package/templates/assistkick-product-system/package.json +4 -2
  6. package/templates/assistkick-product-system/packages/backend/package.json +2 -0
  7. package/templates/assistkick-product-system/packages/backend/src/routes/agents.ts +165 -0
  8. package/templates/assistkick-product-system/packages/backend/src/routes/files.test.ts +358 -0
  9. package/templates/assistkick-product-system/packages/backend/src/routes/files.ts +356 -0
  10. package/templates/assistkick-product-system/packages/backend/src/routes/git.ts +96 -1
  11. package/templates/assistkick-product-system/packages/backend/src/routes/graph.ts +1 -0
  12. package/templates/assistkick-product-system/packages/backend/src/routes/kanban.ts +43 -4
  13. package/templates/assistkick-product-system/packages/backend/src/routes/pipeline.ts +200 -84
  14. package/templates/assistkick-product-system/packages/backend/src/routes/projects.ts +6 -3
  15. package/templates/assistkick-product-system/packages/backend/src/routes/terminal.ts +53 -17
  16. package/templates/assistkick-product-system/packages/backend/src/routes/video.ts +218 -0
  17. package/templates/assistkick-product-system/packages/backend/src/routes/workflow_groups.ts +119 -0
  18. package/templates/assistkick-product-system/packages/backend/src/routes/workflows.ts +154 -0
  19. package/templates/assistkick-product-system/packages/backend/src/server.ts +81 -9
  20. package/templates/assistkick-product-system/packages/backend/src/services/agent_service.test.ts +489 -0
  21. package/templates/assistkick-product-system/packages/backend/src/services/agent_service.ts +416 -0
  22. package/templates/assistkick-product-system/packages/backend/src/services/bundle_service.test.ts +189 -0
  23. package/templates/assistkick-product-system/packages/backend/src/services/bundle_service.ts +182 -0
  24. package/templates/assistkick-product-system/packages/backend/src/services/init.ts +28 -78
  25. package/templates/assistkick-product-system/packages/backend/src/services/project_service.test.ts +16 -0
  26. package/templates/assistkick-product-system/packages/backend/src/services/project_service.ts +73 -2
  27. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.test.ts +4 -4
  28. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.ts +87 -11
  29. package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.test.ts +210 -69
  30. package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.ts +210 -215
  31. package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.test.ts +162 -0
  32. package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.ts +148 -0
  33. package/templates/assistkick-product-system/packages/backend/src/services/terminal_ws_handler.ts +11 -5
  34. package/templates/assistkick-product-system/packages/backend/src/services/tts_service.test.ts +64 -0
  35. package/templates/assistkick-product-system/packages/backend/src/services/tts_service.ts +134 -0
  36. package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.test.ts +256 -0
  37. package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.ts +258 -0
  38. package/templates/assistkick-product-system/packages/backend/src/services/workflow_group_service.ts +106 -0
  39. package/templates/assistkick-product-system/packages/backend/src/services/workflow_service.test.ts +275 -0
  40. package/templates/assistkick-product-system/packages/backend/src/services/workflow_service.ts +222 -0
  41. package/templates/assistkick-product-system/packages/frontend/package-lock.json +3455 -0
  42. package/templates/assistkick-product-system/packages/frontend/package.json +6 -0
  43. package/templates/assistkick-product-system/packages/frontend/src/App.tsx +8 -0
  44. package/templates/assistkick-product-system/packages/frontend/src/api/client.ts +456 -16
  45. package/templates/assistkick-product-system/packages/frontend/src/api/client_files.test.ts +172 -0
  46. package/templates/assistkick-product-system/packages/frontend/src/api/client_video.test.ts +238 -0
  47. package/templates/assistkick-product-system/packages/frontend/src/components/AgentsView.tsx +307 -0
  48. package/templates/assistkick-product-system/packages/frontend/src/components/CoherenceView.tsx +82 -66
  49. package/templates/assistkick-product-system/packages/frontend/src/components/CompositionPlaceholder.tsx +97 -0
  50. package/templates/assistkick-product-system/packages/frontend/src/components/DesignSystemView.tsx +20 -0
  51. package/templates/assistkick-product-system/packages/frontend/src/components/EditorTabBar.tsx +57 -0
  52. package/templates/assistkick-product-system/packages/frontend/src/components/FileTree.tsx +313 -0
  53. package/templates/assistkick-product-system/packages/frontend/src/components/FileTreeContextMenu.tsx +61 -0
  54. package/templates/assistkick-product-system/packages/frontend/src/components/FileTreeInlineInput.tsx +73 -0
  55. package/templates/assistkick-product-system/packages/frontend/src/components/FilesView.tsx +404 -0
  56. package/templates/assistkick-product-system/packages/frontend/src/components/GitRepoModal.tsx +187 -56
  57. package/templates/assistkick-product-system/packages/frontend/src/components/GraphLegend.tsx +71 -73
  58. package/templates/assistkick-product-system/packages/frontend/src/components/GraphSettings.tsx +8 -8
  59. package/templates/assistkick-product-system/packages/frontend/src/components/GraphView.tsx +1 -1
  60. package/templates/assistkick-product-system/packages/frontend/src/components/InviteUserDialog.tsx +15 -11
  61. package/templates/assistkick-product-system/packages/frontend/src/components/KanbanView.tsx +202 -171
  62. package/templates/assistkick-product-system/packages/frontend/src/components/LoginPage.tsx +14 -14
  63. package/templates/assistkick-product-system/packages/frontend/src/components/ProjectSelector.tsx +54 -33
  64. package/templates/assistkick-product-system/packages/frontend/src/components/QaIssueSheet.tsx +32 -49
  65. package/templates/assistkick-product-system/packages/frontend/src/components/SidePanel.tsx +43 -48
  66. package/templates/assistkick-product-system/packages/frontend/src/components/TerminalView.tsx +121 -52
  67. package/templates/assistkick-product-system/packages/frontend/src/components/Toolbar.tsx +20 -14
  68. package/templates/assistkick-product-system/packages/frontend/src/components/UsersView.tsx +52 -52
  69. package/templates/assistkick-product-system/packages/frontend/src/components/VideoGallery.tsx +313 -0
  70. package/templates/assistkick-product-system/packages/frontend/src/components/VideographyView.tsx +250 -0
  71. package/templates/assistkick-product-system/packages/frontend/src/components/WorkflowsView.tsx +474 -0
  72. package/templates/assistkick-product-system/packages/frontend/src/components/ds/AccentBorderList.tsx +53 -0
  73. package/templates/assistkick-product-system/packages/frontend/src/components/ds/Button.tsx +87 -0
  74. package/templates/assistkick-product-system/packages/frontend/src/components/ds/ButtonGroup.tsx +29 -0
  75. package/templates/assistkick-product-system/packages/frontend/src/components/ds/ButtonShowcase.tsx +221 -0
  76. package/templates/assistkick-product-system/packages/frontend/src/components/ds/CardGlass.tsx +141 -0
  77. package/templates/assistkick-product-system/packages/frontend/src/components/ds/CompletionRing.tsx +30 -0
  78. package/templates/assistkick-product-system/packages/frontend/src/components/ds/ContentCard.tsx +34 -0
  79. package/templates/assistkick-product-system/packages/frontend/src/components/ds/IconButton.tsx +74 -0
  80. package/templates/assistkick-product-system/packages/frontend/src/components/ds/KanbanCard.tsx +103 -87
  81. package/templates/assistkick-product-system/packages/frontend/src/components/ds/KanbanCardShowcase.tsx +9 -188
  82. package/templates/assistkick-product-system/packages/frontend/src/components/ds/Kbd.tsx +11 -0
  83. package/templates/assistkick-product-system/packages/frontend/src/components/ds/KindBadge.tsx +21 -0
  84. package/templates/assistkick-product-system/packages/frontend/src/components/ds/NavBarSidekick.tsx +81 -37
  85. package/templates/assistkick-product-system/packages/frontend/src/components/ds/SidePanelShowcase.tsx +370 -0
  86. package/templates/assistkick-product-system/packages/frontend/src/components/ds/SideSheet.tsx +64 -0
  87. package/templates/assistkick-product-system/packages/frontend/src/components/ds/StatusDot.tsx +18 -0
  88. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/CheckCardPositionNode.tsx +36 -0
  89. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/CheckCycleCountNode.tsx +60 -0
  90. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/EndNode.tsx +42 -0
  91. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/GroupNode.tsx +189 -0
  92. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/NodePalette.tsx +123 -0
  93. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/RunAgentNode.tsx +51 -0
  94. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/SetCardMetadataNode.tsx +53 -0
  95. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/StartNode.tsx +18 -0
  96. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/TransitionCardNode.tsx +59 -0
  97. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowCanvas.tsx +335 -0
  98. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowMonitorModal.tsx +634 -0
  99. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/autoLayout.ts +103 -0
  100. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/edgeColors.ts +35 -0
  101. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/monitor_nodes.tsx +208 -0
  102. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/workflow_types.test.ts +119 -0
  103. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/workflow_types.ts +107 -0
  104. package/templates/assistkick-product-system/packages/frontend/src/constants/graph.ts +13 -11
  105. package/templates/assistkick-product-system/packages/frontend/src/hooks/useAutoSave.ts +75 -0
  106. package/templates/assistkick-product-system/packages/frontend/src/hooks/useToast.tsx +16 -3
  107. package/templates/assistkick-product-system/packages/frontend/src/pages/accept_invitation_page.tsx +30 -27
  108. package/templates/assistkick-product-system/packages/frontend/src/pages/forgot_password_page.tsx +18 -15
  109. package/templates/assistkick-product-system/packages/frontend/src/pages/register_page.tsx +21 -18
  110. package/templates/assistkick-product-system/packages/frontend/src/pages/reset_password_page.tsx +28 -25
  111. package/templates/assistkick-product-system/packages/frontend/src/routes/AgentsRoute.tsx +6 -0
  112. package/templates/assistkick-product-system/packages/frontend/src/routes/CoherenceRoute.tsx +1 -1
  113. package/templates/assistkick-product-system/packages/frontend/src/routes/DashboardLayout.tsx +2 -2
  114. package/templates/assistkick-product-system/packages/frontend/src/routes/FilesRoute.tsx +13 -0
  115. package/templates/assistkick-product-system/packages/frontend/src/routes/GraphRoute.tsx +2 -2
  116. package/templates/assistkick-product-system/packages/frontend/src/routes/VideographyRoute.tsx +13 -0
  117. package/templates/assistkick-product-system/packages/frontend/src/routes/WorkflowsRoute.tsx +6 -0
  118. package/templates/assistkick-product-system/packages/frontend/src/stores/useProjectStore.ts +6 -3
  119. package/templates/assistkick-product-system/packages/frontend/src/stores/useSidePanelStore.ts +4 -4
  120. package/templates/assistkick-product-system/packages/frontend/src/styles/index.css +275 -3535
  121. package/templates/assistkick-product-system/packages/frontend/src/utils/auto_save_service.test.ts +167 -0
  122. package/templates/assistkick-product-system/packages/frontend/src/utils/auto_save_service.ts +101 -0
  123. package/templates/assistkick-product-system/packages/frontend/src/utils/composition_matcher.test.ts +42 -0
  124. package/templates/assistkick-product-system/packages/frontend/src/utils/composition_matcher.ts +17 -0
  125. package/templates/assistkick-product-system/packages/frontend/src/utils/file_utils.test.ts +145 -0
  126. package/templates/assistkick-product-system/packages/frontend/src/utils/file_utils.ts +42 -0
  127. package/templates/assistkick-product-system/packages/frontend/src/utils/task_status.test.ts +4 -10
  128. package/templates/assistkick-product-system/packages/frontend/src/utils/task_status.ts +19 -1
  129. package/templates/assistkick-product-system/packages/frontend/vite.config.ts +5 -0
  130. package/templates/assistkick-product-system/packages/shared/db/local.db +0 -0
  131. package/templates/assistkick-product-system/packages/shared/db/migrations/0004_tidy_matthew_murdock.sql +9 -0
  132. package/templates/assistkick-product-system/packages/shared/db/migrations/0005_mysterious_falcon.sql +692 -0
  133. package/templates/assistkick-product-system/packages/shared/db/migrations/0006_next_venom.sql +9 -0
  134. package/templates/assistkick-product-system/packages/shared/db/migrations/0007_deep_barracuda.sql +39 -0
  135. package/templates/assistkick-product-system/packages/shared/db/migrations/0008_puzzling_hannibal_king.sql +1 -0
  136. package/templates/assistkick-product-system/packages/shared/db/migrations/0009_amused_beast.sql +8 -0
  137. package/templates/assistkick-product-system/packages/shared/db/migrations/0010_spotty_moira_mactaggert.sql +9 -0
  138. package/templates/assistkick-product-system/packages/shared/db/migrations/0011_goofy_snowbird.sql +3 -0
  139. package/templates/assistkick-product-system/packages/shared/db/migrations/0011_supreme_doctor_octopus.sql +3 -0
  140. package/templates/assistkick-product-system/packages/shared/db/migrations/0013_reflective_prowler.sql +15 -0
  141. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0004_snapshot.json +921 -0
  142. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0005_snapshot.json +1042 -0
  143. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0006_snapshot.json +1101 -0
  144. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0007_snapshot.json +1336 -0
  145. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0008_snapshot.json +1275 -0
  146. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0009_snapshot.json +1327 -0
  147. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0010_snapshot.json +1393 -0
  148. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0011_snapshot.json +1436 -0
  149. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0013_snapshot.json +1538 -0
  150. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/_journal.json +70 -0
  151. package/templates/assistkick-product-system/packages/shared/db/schema.ts +113 -0
  152. package/templates/assistkick-product-system/packages/shared/lib/claude-service.ts +32 -7
  153. package/templates/assistkick-product-system/packages/shared/lib/constants.ts +9 -0
  154. package/templates/assistkick-product-system/packages/shared/lib/git_workflow.ts +12 -4
  155. package/templates/assistkick-product-system/packages/shared/lib/graph.ts +5 -0
  156. package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.test.ts +1753 -0
  157. package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.ts +1281 -0
  158. package/templates/assistkick-product-system/packages/shared/lib/workflow_orchestrator.ts +211 -0
  159. package/templates/assistkick-product-system/packages/shared/tools/add_node.test.ts +43 -0
  160. package/templates/assistkick-product-system/packages/shared/tools/add_node.ts +13 -2
  161. package/templates/assistkick-product-system/packages/shared/tools/get_kanban.ts +1 -1
  162. package/templates/assistkick-product-system/packages/shared/tools/migrate_epics.test.ts +226 -0
  163. package/templates/assistkick-product-system/packages/shared/tools/migrate_epics.ts +251 -0
  164. package/templates/assistkick-product-system/packages/shared/tools/update_node.ts +2 -2
  165. package/templates/assistkick-product-system/packages/shared/utils/hello_workflow.test.ts +10 -0
  166. package/templates/assistkick-product-system/packages/shared/utils/hello_workflow.ts +6 -0
  167. package/templates/assistkick-product-system/packages/video/Root.tsx +85 -0
  168. package/templates/assistkick-product-system/packages/video/components/email_scene.tsx +231 -0
  169. package/templates/assistkick-product-system/packages/video/components/outro_scene.tsx +153 -0
  170. package/templates/assistkick-product-system/packages/video/components/part_divider.tsx +90 -0
  171. package/templates/assistkick-product-system/packages/video/components/scene.tsx +226 -0
  172. package/templates/assistkick-product-system/packages/video/components/theme.ts +22 -0
  173. package/templates/assistkick-product-system/packages/video/components/title_scene.tsx +169 -0
  174. package/templates/assistkick-product-system/packages/video/components/video_split_layout.tsx +84 -0
  175. package/templates/assistkick-product-system/packages/video/compositions/.gitkeep +0 -0
  176. package/templates/assistkick-product-system/packages/video/index.ts +4 -0
  177. package/templates/assistkick-product-system/packages/video/package.json +28 -0
  178. package/templates/assistkick-product-system/packages/video/remotion.config.ts +11 -0
  179. package/templates/assistkick-product-system/packages/video/scripts/process_script.test.ts +326 -0
  180. package/templates/assistkick-product-system/packages/video/scripts/process_script.ts +630 -0
  181. package/templates/assistkick-product-system/packages/video/style.css +1 -0
  182. package/templates/assistkick-product-system/packages/video/tsconfig.json +18 -0
  183. package/templates/assistkick-product-system/tests/graph_legend.test.ts +2 -1
  184. package/templates/assistkick-product-system/tests/video_render_service.test.ts +179 -0
  185. package/templates/assistkick-product-system/tests/web_terminal.test.ts +219 -455
  186. package/templates/assistkick-product-system/tests/workflow_integration.test.ts +341 -0
  187. package/templates/skills/assistkick-developer/SKILL.md +3 -0
  188. package/templates/skills/assistkick-developer/references/react_development_guidelines.md +225 -0
  189. package/templates/skills/product-system/graph.json +1890 -0
  190. package/templates/skills/product-system/kanban.json +304 -0
  191. package/templates/skills/product-system/nodes/comp_001.md +56 -0
  192. package/templates/skills/product-system/nodes/comp_002.md +57 -0
  193. package/templates/skills/product-system/nodes/data_001.md +51 -0
  194. package/templates/skills/product-system/nodes/data_002.md +40 -0
  195. package/templates/skills/product-system/nodes/data_004.md +38 -0
  196. package/templates/skills/product-system/nodes/dec_001.md +34 -0
  197. package/templates/skills/product-system/nodes/dec_016.md +32 -0
  198. package/templates/skills/product-system/nodes/feat_008.md +30 -0
  199. package/templates/skills/video-composition-agent/SKILL.md +232 -0
  200. package/templates/skills/video-script-writer/SKILL.md +136 -0
@@ -0,0 +1,335 @@
1
+ import React, { useCallback, useRef, useMemo, useState } from 'react';
2
+ import {
3
+ ReactFlow,
4
+ MiniMap,
5
+ Background,
6
+ BackgroundVariant,
7
+ Controls,
8
+ Panel,
9
+ addEdge,
10
+ useNodesState,
11
+ useEdgesState,
12
+ useReactFlow,
13
+ type Connection,
14
+ type Edge,
15
+ type Node,
16
+ type NodeTypes,
17
+ type OnConnect,
18
+ ReactFlowProvider,
19
+ } from '@xyflow/react';
20
+ import '@xyflow/react/dist/style.css';
21
+ import { LayoutGrid } from 'lucide-react';
22
+ import { StartNode } from './StartNode';
23
+ import { EndNode } from './EndNode';
24
+ import { TransitionCardNode } from './TransitionCardNode';
25
+ import { RunAgentNode } from './RunAgentNode';
26
+ import { CheckCardPositionNode } from './CheckCardPositionNode';
27
+ import { CheckCycleCountNode } from './CheckCycleCountNode';
28
+ import { SetCardMetadataNode } from './SetCardMetadataNode';
29
+ import { GroupNode } from './GroupNode';
30
+ import { NODE_DEFAULTS } from './workflow_types';
31
+ import type { WorkflowNodeType } from './workflow_types';
32
+ import { autoLayoutNodes } from './autoLayout';
33
+ import { colorizeEdges } from './edgeColors';
34
+
35
+ const NODE_TYPE_MAP: NodeTypes = {
36
+ start: StartNode,
37
+ end: EndNode,
38
+ transitionCard: TransitionCardNode,
39
+ runAgent: RunAgentNode,
40
+ checkCardPosition: CheckCardPositionNode,
41
+ checkCycleCount: CheckCycleCountNode,
42
+ setCardMetadata: SetCardMetadataNode,
43
+ group: GroupNode,
44
+ };
45
+
46
+ interface ContextMenuState {
47
+ x: number;
48
+ y: number;
49
+ selectedNodeIds: string[];
50
+ }
51
+
52
+ interface WorkflowCanvasProps {
53
+ initialNodes: Node[];
54
+ initialEdges: Edge[];
55
+ onNodesChange?: (nodes: Node[]) => void;
56
+ onEdgesChange?: (edges: Edge[]) => void;
57
+ onChange?: (nodes: Node[], edges: Edge[]) => void;
58
+ onSaveAsGroup?: (name: string, nodes: Node[], edges: Edge[], inputHandles: string[], outputHandles: string[]) => void;
59
+ }
60
+
61
+ export function WorkflowCanvasInner({
62
+ initialNodes,
63
+ initialEdges,
64
+ onChange,
65
+ onSaveAsGroup,
66
+ }: WorkflowCanvasProps) {
67
+ const reactFlowWrapper = useRef<HTMLDivElement>(null);
68
+ const nodeIdCounter = useRef(0);
69
+ const getNextNodeId = useCallback(() => `node_${Date.now()}_${++nodeIdCounter.current}`, []);
70
+ const [nodes, setNodes, onNodesChangeHandler] = useNodesState(initialNodes);
71
+ const [edges, setEdges, onEdgesChangeHandler] = useEdgesState(initialEdges);
72
+ const { fitView } = useReactFlow();
73
+ const [contextMenu, setContextMenu] = useState<ContextMenuState | null>(null);
74
+
75
+ const nodeTypes = useMemo(() => NODE_TYPE_MAP, []);
76
+
77
+ // Compute colored edges — each source node's outgoing edges get distinct colors
78
+ const coloredEdges = useMemo(() => colorizeEdges(edges), [edges]);
79
+
80
+ const onConnect: OnConnect = useCallback(
81
+ (connection: Connection) => {
82
+ setEdges((eds) => addEdge(connection, eds));
83
+ },
84
+ [setEdges],
85
+ );
86
+
87
+ const handleNodesChange = useCallback(
88
+ (changes: any) => {
89
+ onNodesChangeHandler(changes);
90
+ },
91
+ [onNodesChangeHandler],
92
+ );
93
+
94
+ const handleEdgesChange = useCallback(
95
+ (changes: any) => {
96
+ onEdgesChangeHandler(changes);
97
+ },
98
+ [onEdgesChangeHandler],
99
+ );
100
+
101
+ // Notify parent of changes
102
+ const nodesRef = useRef(nodes);
103
+ const edgesRef = useRef(edges);
104
+ nodesRef.current = nodes;
105
+ edgesRef.current = edges;
106
+
107
+ const notifyChange = useCallback(() => {
108
+ onChange?.(nodesRef.current, edgesRef.current);
109
+ }, [onChange]);
110
+
111
+ const onDragOver = useCallback((event: React.DragEvent) => {
112
+ event.preventDefault();
113
+ event.dataTransfer.dropEffect = 'move';
114
+ }, []);
115
+
116
+ const { screenToFlowPosition } = useReactFlow();
117
+
118
+ const onDrop = useCallback(
119
+ (event: React.DragEvent) => {
120
+ event.preventDefault();
121
+ const type = event.dataTransfer.getData('application/reactflow') as WorkflowNodeType;
122
+ const groupDataStr = event.dataTransfer.getData('application/workflow-group');
123
+
124
+ if (groupDataStr) {
125
+ // Dropping a reusable group from the palette
126
+ const groupData = JSON.parse(groupDataStr);
127
+ const position = screenToFlowPosition({
128
+ x: event.clientX,
129
+ y: event.clientY,
130
+ });
131
+
132
+ const newNode: Node = {
133
+ id: getNextNodeId(),
134
+ type: 'group',
135
+ position,
136
+ data: {
137
+ groupId: groupData.id,
138
+ groupName: groupData.name,
139
+ internalNodes: JSON.parse(groupData.graphData).nodes || [],
140
+ internalEdges: JSON.parse(groupData.graphData).edges || [],
141
+ inputHandles: JSON.parse(groupData.graphData).inputHandles || ['input'],
142
+ outputHandles: JSON.parse(groupData.graphData).outputHandles || ['output'],
143
+ },
144
+ };
145
+
146
+ setNodes((nds) => [...nds, newNode]);
147
+ return;
148
+ }
149
+
150
+ if (!type) return;
151
+
152
+ const position = screenToFlowPosition({
153
+ x: event.clientX,
154
+ y: event.clientY,
155
+ });
156
+
157
+ const defaults = NODE_DEFAULTS[type];
158
+ if (!defaults) return;
159
+
160
+ const newNode: Node = {
161
+ id: getNextNodeId(),
162
+ type,
163
+ position,
164
+ data: defaults(),
165
+ };
166
+
167
+ setNodes((nds) => [...nds, newNode]);
168
+ },
169
+ [setNodes, screenToFlowPosition, getNextNodeId],
170
+ );
171
+
172
+ const handleAutoLayout = useCallback(() => {
173
+ setNodes((currentNodes) => autoLayoutNodes(currentNodes, edgesRef.current));
174
+ setTimeout(() => fitView({ padding: 0.2, duration: 300 }), 50);
175
+ }, [setNodes, fitView]);
176
+
177
+ // Expose current state via a callback whenever nodes/edges change
178
+ React.useEffect(() => {
179
+ notifyChange();
180
+ }, [nodes, edges, notifyChange]);
181
+
182
+ // Context menu on right-click when multiple nodes selected
183
+ const onNodeContextMenu = useCallback(
184
+ (event: React.MouseEvent, node: Node) => {
185
+ event.preventDefault();
186
+ const selectedIds = nodesRef.current
187
+ .filter((n) => n.selected)
188
+ .map((n) => n.id);
189
+
190
+ // Only show context menu if 2+ nodes are selected
191
+ if (selectedIds.length < 2) {
192
+ setContextMenu(null);
193
+ return;
194
+ }
195
+
196
+ // Ensure the right-clicked node is in the selection
197
+ if (!selectedIds.includes(node.id)) {
198
+ setContextMenu(null);
199
+ return;
200
+ }
201
+
202
+ setContextMenu({
203
+ x: event.clientX,
204
+ y: event.clientY,
205
+ selectedNodeIds: selectedIds,
206
+ });
207
+ },
208
+ [],
209
+ );
210
+
211
+ const onPaneClick = useCallback(() => {
212
+ setContextMenu(null);
213
+ }, []);
214
+
215
+ const handleSaveAsGroup = useCallback(() => {
216
+ if (!contextMenu || !onSaveAsGroup) return;
217
+
218
+ const selectedIds = new Set(contextMenu.selectedNodeIds);
219
+ const selectedNodes = nodesRef.current.filter((n) => selectedIds.has(n.id));
220
+
221
+ // Find internal edges (both endpoints in selection)
222
+ const internalEdges = edgesRef.current.filter(
223
+ (e) => selectedIds.has(e.source) && selectedIds.has(e.target),
224
+ );
225
+
226
+ // Find external connection points:
227
+ // Input handles: edges coming INTO the selection from outside
228
+ // Output handles: edges going OUT from the selection to outside
229
+ const incomingEdges = edgesRef.current.filter(
230
+ (e) => !selectedIds.has(e.source) && selectedIds.has(e.target),
231
+ );
232
+ const outgoingEdges = edgesRef.current.filter(
233
+ (e) => selectedIds.has(e.source) && !selectedIds.has(e.target),
234
+ );
235
+
236
+ const inputHandles = incomingEdges.length > 0
237
+ ? incomingEdges.map((e) => e.targetHandle || 'input')
238
+ : ['input'];
239
+ const outputHandles = outgoingEdges.length > 0
240
+ ? outgoingEdges.map((e) => e.sourceHandle || 'output')
241
+ : ['output'];
242
+
243
+ // Deduplicate handles
244
+ const uniqueInputs = [...new Set(inputHandles)];
245
+ const uniqueOutputs = [...new Set(outputHandles)];
246
+
247
+ // Normalize node positions relative to the group's top-left
248
+ const minX = Math.min(...selectedNodes.map((n) => n.position.x));
249
+ const minY = Math.min(...selectedNodes.map((n) => n.position.y));
250
+ const normalizedNodes = selectedNodes.map((n) => ({
251
+ ...n,
252
+ position: { x: n.position.x - minX, y: n.position.y - minY },
253
+ selected: false,
254
+ }));
255
+
256
+ const name = prompt('Enter a name for this reusable group:');
257
+ if (!name || !name.trim()) {
258
+ setContextMenu(null);
259
+ return;
260
+ }
261
+
262
+ onSaveAsGroup(name.trim(), normalizedNodes, internalEdges, uniqueInputs, uniqueOutputs);
263
+ setContextMenu(null);
264
+ }, [contextMenu, onSaveAsGroup]);
265
+
266
+ return (
267
+ <div ref={reactFlowWrapper} className="flex-1 h-full relative">
268
+ <ReactFlow
269
+ nodes={nodes}
270
+ edges={coloredEdges}
271
+ onNodesChange={handleNodesChange}
272
+ onEdgesChange={handleEdgesChange}
273
+ onConnect={onConnect}
274
+ onDragOver={onDragOver}
275
+ onDrop={onDrop}
276
+ nodeTypes={nodeTypes}
277
+ onNodeContextMenu={onNodeContextMenu}
278
+ onPaneClick={onPaneClick}
279
+ selectionMode={1 /* SelectionMode.Partial */}
280
+ multiSelectionKeyCode="Shift"
281
+ snapToGrid
282
+ snapGrid={[16, 16]}
283
+ fitView
284
+ deleteKeyCode={['Backspace', 'Delete']}
285
+ defaultEdgeOptions={{
286
+ type: 'smoothstep',
287
+ animated: true,
288
+ }}
289
+ className="workflow-canvas"
290
+ >
291
+ <Background variant={BackgroundVariant.Dots} gap={16} size={1} color="var(--border-color)" />
292
+ <MiniMap
293
+ nodeStrokeColor="var(--accent)"
294
+ nodeColor="var(--bg-tertiary)"
295
+ maskColor="rgba(0,0,0,0.3)"
296
+ className="!bg-surface !border !border-edge !rounded-lg"
297
+ />
298
+ <Controls className="!bg-surface !border !border-edge !rounded-lg !shadow-none [&>button]:!bg-surface [&>button]:!border-edge [&>button]:!text-content [&>button:hover]:!bg-surface-raised" />
299
+ <Panel position="top-right">
300
+ <button
301
+ onClick={handleAutoLayout}
302
+ className="flex items-center gap-1.5 px-3 py-1.5 rounded-md bg-surface border border-edge text-[12px] text-content hover:bg-surface-raised transition-colors shadow-sm"
303
+ title="Auto-layout nodes as a tree"
304
+ >
305
+ <LayoutGrid size={14} />
306
+ Auto Layout
307
+ </button>
308
+ </Panel>
309
+ </ReactFlow>
310
+
311
+ {/* Context menu for multi-selection */}
312
+ {contextMenu && (
313
+ <div
314
+ className="fixed z-50 bg-surface border border-edge rounded-lg shadow-lg py-1 min-w-[180px]"
315
+ style={{ left: contextMenu.x, top: contextMenu.y }}
316
+ >
317
+ <button
318
+ onClick={handleSaveAsGroup}
319
+ className="w-full text-left px-3 py-2 text-[12px] text-content hover:bg-surface-raised transition-colors"
320
+ >
321
+ Save as Reusable Group ({contextMenu.selectedNodeIds.length} nodes)
322
+ </button>
323
+ </div>
324
+ )}
325
+ </div>
326
+ );
327
+ }
328
+
329
+ export function WorkflowCanvas(props: WorkflowCanvasProps) {
330
+ return (
331
+ <ReactFlowProvider>
332
+ <WorkflowCanvasInner {...props} />
333
+ </ReactFlowProvider>
334
+ );
335
+ }