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