@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.
- package/dist/bin/create.js +0 -0
- package/package.json +9 -7
- package/templates/assistkick-product-system/.env.example +1 -0
- package/templates/assistkick-product-system/local.db +0 -0
- package/templates/assistkick-product-system/package.json +4 -2
- package/templates/assistkick-product-system/packages/backend/package.json +2 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/agents.ts +165 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/files.test.ts +358 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/files.ts +356 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/git.ts +96 -1
- package/templates/assistkick-product-system/packages/backend/src/routes/graph.ts +1 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/kanban.ts +43 -4
- package/templates/assistkick-product-system/packages/backend/src/routes/pipeline.ts +200 -84
- package/templates/assistkick-product-system/packages/backend/src/routes/projects.ts +6 -3
- package/templates/assistkick-product-system/packages/backend/src/routes/terminal.ts +53 -17
- package/templates/assistkick-product-system/packages/backend/src/routes/video.ts +218 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/workflow_groups.ts +119 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/workflows.ts +154 -0
- package/templates/assistkick-product-system/packages/backend/src/server.ts +81 -9
- package/templates/assistkick-product-system/packages/backend/src/services/agent_service.test.ts +489 -0
- package/templates/assistkick-product-system/packages/backend/src/services/agent_service.ts +416 -0
- package/templates/assistkick-product-system/packages/backend/src/services/bundle_service.test.ts +189 -0
- package/templates/assistkick-product-system/packages/backend/src/services/bundle_service.ts +182 -0
- package/templates/assistkick-product-system/packages/backend/src/services/init.ts +28 -78
- package/templates/assistkick-product-system/packages/backend/src/services/project_service.test.ts +16 -0
- package/templates/assistkick-product-system/packages/backend/src/services/project_service.ts +73 -2
- package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.test.ts +4 -4
- package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.ts +87 -11
- package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.test.ts +210 -69
- package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.ts +210 -215
- package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.test.ts +162 -0
- package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.ts +148 -0
- package/templates/assistkick-product-system/packages/backend/src/services/terminal_ws_handler.ts +11 -5
- package/templates/assistkick-product-system/packages/backend/src/services/tts_service.test.ts +64 -0
- package/templates/assistkick-product-system/packages/backend/src/services/tts_service.ts +134 -0
- package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.test.ts +256 -0
- package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.ts +258 -0
- package/templates/assistkick-product-system/packages/backend/src/services/workflow_group_service.ts +106 -0
- package/templates/assistkick-product-system/packages/backend/src/services/workflow_service.test.ts +275 -0
- package/templates/assistkick-product-system/packages/backend/src/services/workflow_service.ts +222 -0
- package/templates/assistkick-product-system/packages/frontend/package-lock.json +3455 -0
- package/templates/assistkick-product-system/packages/frontend/package.json +6 -0
- package/templates/assistkick-product-system/packages/frontend/src/App.tsx +8 -0
- package/templates/assistkick-product-system/packages/frontend/src/api/client.ts +456 -16
- package/templates/assistkick-product-system/packages/frontend/src/api/client_files.test.ts +172 -0
- package/templates/assistkick-product-system/packages/frontend/src/api/client_video.test.ts +238 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/AgentsView.tsx +307 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/CoherenceView.tsx +82 -66
- package/templates/assistkick-product-system/packages/frontend/src/components/CompositionPlaceholder.tsx +97 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/DesignSystemView.tsx +20 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/EditorTabBar.tsx +57 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/FileTree.tsx +313 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/FileTreeContextMenu.tsx +61 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/FileTreeInlineInput.tsx +73 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/FilesView.tsx +404 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/GitRepoModal.tsx +187 -56
- package/templates/assistkick-product-system/packages/frontend/src/components/GraphLegend.tsx +71 -73
- package/templates/assistkick-product-system/packages/frontend/src/components/GraphSettings.tsx +8 -8
- package/templates/assistkick-product-system/packages/frontend/src/components/GraphView.tsx +1 -1
- package/templates/assistkick-product-system/packages/frontend/src/components/InviteUserDialog.tsx +15 -11
- package/templates/assistkick-product-system/packages/frontend/src/components/KanbanView.tsx +202 -171
- package/templates/assistkick-product-system/packages/frontend/src/components/LoginPage.tsx +14 -14
- package/templates/assistkick-product-system/packages/frontend/src/components/ProjectSelector.tsx +54 -33
- package/templates/assistkick-product-system/packages/frontend/src/components/QaIssueSheet.tsx +32 -49
- package/templates/assistkick-product-system/packages/frontend/src/components/SidePanel.tsx +43 -48
- package/templates/assistkick-product-system/packages/frontend/src/components/TerminalView.tsx +121 -52
- package/templates/assistkick-product-system/packages/frontend/src/components/Toolbar.tsx +20 -14
- package/templates/assistkick-product-system/packages/frontend/src/components/UsersView.tsx +52 -52
- package/templates/assistkick-product-system/packages/frontend/src/components/VideoGallery.tsx +313 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/VideographyView.tsx +250 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/WorkflowsView.tsx +474 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/AccentBorderList.tsx +53 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/Button.tsx +87 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/ButtonGroup.tsx +29 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/ButtonShowcase.tsx +221 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/CardGlass.tsx +141 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/CompletionRing.tsx +30 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/ContentCard.tsx +34 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/IconButton.tsx +74 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/KanbanCard.tsx +103 -87
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/KanbanCardShowcase.tsx +9 -188
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/Kbd.tsx +11 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/KindBadge.tsx +21 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/NavBarSidekick.tsx +81 -37
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/SidePanelShowcase.tsx +370 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/SideSheet.tsx +64 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/StatusDot.tsx +18 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/CheckCardPositionNode.tsx +36 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/CheckCycleCountNode.tsx +60 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/EndNode.tsx +42 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/GroupNode.tsx +189 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/NodePalette.tsx +123 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/RunAgentNode.tsx +51 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/SetCardMetadataNode.tsx +53 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/StartNode.tsx +18 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/TransitionCardNode.tsx +59 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowCanvas.tsx +335 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowMonitorModal.tsx +634 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/autoLayout.ts +103 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/edgeColors.ts +35 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/monitor_nodes.tsx +208 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/workflow_types.test.ts +119 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/workflow_types.ts +107 -0
- package/templates/assistkick-product-system/packages/frontend/src/constants/graph.ts +13 -11
- package/templates/assistkick-product-system/packages/frontend/src/hooks/useAutoSave.ts +75 -0
- package/templates/assistkick-product-system/packages/frontend/src/hooks/useToast.tsx +16 -3
- package/templates/assistkick-product-system/packages/frontend/src/pages/accept_invitation_page.tsx +30 -27
- package/templates/assistkick-product-system/packages/frontend/src/pages/forgot_password_page.tsx +18 -15
- package/templates/assistkick-product-system/packages/frontend/src/pages/register_page.tsx +21 -18
- package/templates/assistkick-product-system/packages/frontend/src/pages/reset_password_page.tsx +28 -25
- package/templates/assistkick-product-system/packages/frontend/src/routes/AgentsRoute.tsx +6 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/CoherenceRoute.tsx +1 -1
- package/templates/assistkick-product-system/packages/frontend/src/routes/DashboardLayout.tsx +2 -2
- package/templates/assistkick-product-system/packages/frontend/src/routes/FilesRoute.tsx +13 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/GraphRoute.tsx +2 -2
- package/templates/assistkick-product-system/packages/frontend/src/routes/VideographyRoute.tsx +13 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/WorkflowsRoute.tsx +6 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useProjectStore.ts +6 -3
- package/templates/assistkick-product-system/packages/frontend/src/stores/useSidePanelStore.ts +4 -4
- package/templates/assistkick-product-system/packages/frontend/src/styles/index.css +275 -3535
- package/templates/assistkick-product-system/packages/frontend/src/utils/auto_save_service.test.ts +167 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/auto_save_service.ts +101 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/composition_matcher.test.ts +42 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/composition_matcher.ts +17 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/file_utils.test.ts +145 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/file_utils.ts +42 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/task_status.test.ts +4 -10
- package/templates/assistkick-product-system/packages/frontend/src/utils/task_status.ts +19 -1
- package/templates/assistkick-product-system/packages/frontend/vite.config.ts +5 -0
- package/templates/assistkick-product-system/packages/shared/db/local.db +0 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0004_tidy_matthew_murdock.sql +9 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0005_mysterious_falcon.sql +692 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0006_next_venom.sql +9 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0007_deep_barracuda.sql +39 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0008_puzzling_hannibal_king.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0009_amused_beast.sql +8 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0010_spotty_moira_mactaggert.sql +9 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0011_goofy_snowbird.sql +3 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0011_supreme_doctor_octopus.sql +3 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0013_reflective_prowler.sql +15 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0004_snapshot.json +921 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0005_snapshot.json +1042 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0006_snapshot.json +1101 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0007_snapshot.json +1336 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0008_snapshot.json +1275 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0009_snapshot.json +1327 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0010_snapshot.json +1393 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0011_snapshot.json +1436 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0013_snapshot.json +1538 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/_journal.json +70 -0
- package/templates/assistkick-product-system/packages/shared/db/schema.ts +113 -0
- package/templates/assistkick-product-system/packages/shared/lib/claude-service.ts +32 -7
- package/templates/assistkick-product-system/packages/shared/lib/constants.ts +9 -0
- package/templates/assistkick-product-system/packages/shared/lib/git_workflow.ts +12 -4
- package/templates/assistkick-product-system/packages/shared/lib/graph.ts +5 -0
- package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.test.ts +1753 -0
- package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.ts +1281 -0
- package/templates/assistkick-product-system/packages/shared/lib/workflow_orchestrator.ts +211 -0
- package/templates/assistkick-product-system/packages/shared/tools/add_node.test.ts +43 -0
- package/templates/assistkick-product-system/packages/shared/tools/add_node.ts +13 -2
- package/templates/assistkick-product-system/packages/shared/tools/get_kanban.ts +1 -1
- package/templates/assistkick-product-system/packages/shared/tools/migrate_epics.test.ts +226 -0
- package/templates/assistkick-product-system/packages/shared/tools/migrate_epics.ts +251 -0
- package/templates/assistkick-product-system/packages/shared/tools/update_node.ts +2 -2
- package/templates/assistkick-product-system/packages/shared/utils/hello_workflow.test.ts +10 -0
- package/templates/assistkick-product-system/packages/shared/utils/hello_workflow.ts +6 -0
- package/templates/assistkick-product-system/packages/video/Root.tsx +85 -0
- package/templates/assistkick-product-system/packages/video/components/email_scene.tsx +231 -0
- package/templates/assistkick-product-system/packages/video/components/outro_scene.tsx +153 -0
- package/templates/assistkick-product-system/packages/video/components/part_divider.tsx +90 -0
- package/templates/assistkick-product-system/packages/video/components/scene.tsx +226 -0
- package/templates/assistkick-product-system/packages/video/components/theme.ts +22 -0
- package/templates/assistkick-product-system/packages/video/components/title_scene.tsx +169 -0
- package/templates/assistkick-product-system/packages/video/components/video_split_layout.tsx +84 -0
- package/templates/assistkick-product-system/packages/video/compositions/.gitkeep +0 -0
- package/templates/assistkick-product-system/packages/video/index.ts +4 -0
- package/templates/assistkick-product-system/packages/video/package.json +28 -0
- package/templates/assistkick-product-system/packages/video/remotion.config.ts +11 -0
- package/templates/assistkick-product-system/packages/video/scripts/process_script.test.ts +326 -0
- package/templates/assistkick-product-system/packages/video/scripts/process_script.ts +630 -0
- package/templates/assistkick-product-system/packages/video/style.css +1 -0
- package/templates/assistkick-product-system/packages/video/tsconfig.json +18 -0
- package/templates/assistkick-product-system/tests/graph_legend.test.ts +2 -1
- package/templates/assistkick-product-system/tests/video_render_service.test.ts +179 -0
- package/templates/assistkick-product-system/tests/web_terminal.test.ts +219 -455
- package/templates/assistkick-product-system/tests/workflow_integration.test.ts +341 -0
- package/templates/skills/assistkick-developer/SKILL.md +3 -0
- package/templates/skills/assistkick-developer/references/react_development_guidelines.md +225 -0
- package/templates/skills/product-system/graph.json +1890 -0
- package/templates/skills/product-system/kanban.json +304 -0
- package/templates/skills/product-system/nodes/comp_001.md +56 -0
- package/templates/skills/product-system/nodes/comp_002.md +57 -0
- package/templates/skills/product-system/nodes/data_001.md +51 -0
- package/templates/skills/product-system/nodes/data_002.md +40 -0
- package/templates/skills/product-system/nodes/data_004.md +38 -0
- package/templates/skills/product-system/nodes/dec_001.md +34 -0
- package/templates/skills/product-system/nodes/dec_016.md +32 -0
- package/templates/skills/product-system/nodes/feat_008.md +30 -0
- package/templates/skills/video-composition-agent/SKILL.md +232 -0
- package/templates/skills/video-script-writer/SKILL.md +136 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React, { useCallback } from 'react';
|
|
2
|
+
import { Handle, Position, useReactFlow } from '@xyflow/react';
|
|
3
|
+
import type { NodeProps } from '@xyflow/react';
|
|
4
|
+
import { RefreshCw } from 'lucide-react';
|
|
5
|
+
|
|
6
|
+
export function CheckCycleCountNode({ id, data, selected }: NodeProps) {
|
|
7
|
+
const { updateNodeData } = useReactFlow();
|
|
8
|
+
const maxCount = (data.maxCount as number) || 3;
|
|
9
|
+
|
|
10
|
+
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
|
11
|
+
const val = parseInt(e.target.value, 10);
|
|
12
|
+
if (!isNaN(val) && val > 0) {
|
|
13
|
+
updateNodeData(id, { maxCount: val });
|
|
14
|
+
}
|
|
15
|
+
}, [id, updateNodeData]);
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div className={`px-4 py-3 rounded-lg border bg-surface-raised text-content min-w-[180px] ${
|
|
19
|
+
selected ? 'border-accent shadow-[0_0_0_1px_var(--accent)]' : 'border-edge'
|
|
20
|
+
}`}>
|
|
21
|
+
<Handle type="target" position={Position.Top} className="!w-3 !h-3 !bg-accent !border-2 !border-surface" />
|
|
22
|
+
<div className="flex items-center gap-2 mb-2">
|
|
23
|
+
<RefreshCw size={14} className="text-orange-400" />
|
|
24
|
+
<span className="text-[12px] font-semibold">Check Cycle Count</span>
|
|
25
|
+
</div>
|
|
26
|
+
<div className="mb-2">
|
|
27
|
+
<label className="text-[10px] text-content-muted uppercase tracking-wider">Max Count</label>
|
|
28
|
+
<input
|
|
29
|
+
type="number"
|
|
30
|
+
min={1}
|
|
31
|
+
value={maxCount}
|
|
32
|
+
onChange={handleChange}
|
|
33
|
+
className="w-full px-2 py-1 rounded border border-edge bg-surface text-[11px] text-content"
|
|
34
|
+
/>
|
|
35
|
+
</div>
|
|
36
|
+
<div className="flex justify-between text-[9px] text-content-muted">
|
|
37
|
+
<div className="relative">
|
|
38
|
+
<span className="px-1.5 py-0.5 rounded bg-surface border border-edge">under limit</span>
|
|
39
|
+
<Handle
|
|
40
|
+
type="source"
|
|
41
|
+
position={Position.Bottom}
|
|
42
|
+
id="under_limit"
|
|
43
|
+
className="!w-2 !h-2 !bg-green-400 !border !border-surface"
|
|
44
|
+
style={{ left: '30%' }}
|
|
45
|
+
/>
|
|
46
|
+
</div>
|
|
47
|
+
<div className="relative">
|
|
48
|
+
<span className="px-1.5 py-0.5 rounded bg-surface border border-edge">at limit</span>
|
|
49
|
+
<Handle
|
|
50
|
+
type="source"
|
|
51
|
+
position={Position.Bottom}
|
|
52
|
+
id="at_limit"
|
|
53
|
+
className="!w-2 !h-2 !bg-red-400 !border !border-surface"
|
|
54
|
+
style={{ left: '70%' }}
|
|
55
|
+
/>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
}
|
package/templates/assistkick-product-system/packages/frontend/src/components/workflow/EndNode.tsx
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React, { useCallback } from 'react';
|
|
2
|
+
import { Handle, Position, useReactFlow } from '@xyflow/react';
|
|
3
|
+
import type { NodeProps } from '@xyflow/react';
|
|
4
|
+
import { Square } from 'lucide-react';
|
|
5
|
+
import { END_STATUS_TYPES } from './workflow_types';
|
|
6
|
+
import type { EndStatusType } from './workflow_types';
|
|
7
|
+
|
|
8
|
+
const STATUS_COLORS: Record<EndStatusType, string> = {
|
|
9
|
+
success: 'text-accent',
|
|
10
|
+
blocked: 'text-amber-400',
|
|
11
|
+
failed: 'text-error',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function EndNode({ id, data, selected }: NodeProps) {
|
|
15
|
+
const { updateNodeData } = useReactFlow();
|
|
16
|
+
const statusType = (data.statusType as EndStatusType) || 'success';
|
|
17
|
+
|
|
18
|
+
const handleChange = useCallback((e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
19
|
+
updateNodeData(id, { statusType: e.target.value });
|
|
20
|
+
}, [id, updateNodeData]);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div className={`px-4 py-3 rounded-lg border bg-surface-raised text-content min-w-[160px] ${
|
|
24
|
+
selected ? 'border-accent shadow-[0_0_0_1px_var(--accent)]' : 'border-edge'
|
|
25
|
+
}`}>
|
|
26
|
+
<Handle type="target" position={Position.Top} className="!w-3 !h-3 !bg-accent !border-2 !border-surface" />
|
|
27
|
+
<div className="flex items-center gap-2 mb-2">
|
|
28
|
+
<Square size={14} className={STATUS_COLORS[statusType]} />
|
|
29
|
+
<span className="text-[12px] font-semibold uppercase tracking-wider">End</span>
|
|
30
|
+
</div>
|
|
31
|
+
<select
|
|
32
|
+
value={statusType}
|
|
33
|
+
onChange={handleChange}
|
|
34
|
+
className="w-full px-2 py-1 rounded border border-edge bg-surface text-[11px] text-content appearance-none"
|
|
35
|
+
>
|
|
36
|
+
{END_STATUS_TYPES.map(s => (
|
|
37
|
+
<option key={s} value={s}>{s}</option>
|
|
38
|
+
))}
|
|
39
|
+
</select>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
package/templates/assistkick-product-system/packages/frontend/src/components/workflow/GroupNode.tsx
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import React, { useState, useCallback, useMemo } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Handle,
|
|
4
|
+
Position,
|
|
5
|
+
ReactFlow,
|
|
6
|
+
Background,
|
|
7
|
+
BackgroundVariant,
|
|
8
|
+
MiniMap,
|
|
9
|
+
useNodesState,
|
|
10
|
+
useEdgesState,
|
|
11
|
+
addEdge,
|
|
12
|
+
ReactFlowProvider,
|
|
13
|
+
type Connection,
|
|
14
|
+
type NodeTypes,
|
|
15
|
+
type NodeProps,
|
|
16
|
+
} from '@xyflow/react';
|
|
17
|
+
import { Layers, Maximize2, Minimize2 } from 'lucide-react';
|
|
18
|
+
import { StartNode } from './StartNode';
|
|
19
|
+
import { EndNode } from './EndNode';
|
|
20
|
+
import { TransitionCardNode } from './TransitionCardNode';
|
|
21
|
+
import { RunAgentNode } from './RunAgentNode';
|
|
22
|
+
import { CheckCardPositionNode } from './CheckCardPositionNode';
|
|
23
|
+
import { CheckCycleCountNode } from './CheckCycleCountNode';
|
|
24
|
+
import { SetCardMetadataNode } from './SetCardMetadataNode';
|
|
25
|
+
|
|
26
|
+
/** Node types available inside the group sub-canvas (no nested groups). */
|
|
27
|
+
const INNER_NODE_TYPE_MAP: NodeTypes = {
|
|
28
|
+
start: StartNode,
|
|
29
|
+
end: EndNode,
|
|
30
|
+
transitionCard: TransitionCardNode,
|
|
31
|
+
runAgent: RunAgentNode,
|
|
32
|
+
checkCardPosition: CheckCardPositionNode,
|
|
33
|
+
checkCycleCount: CheckCycleCountNode,
|
|
34
|
+
setCardMetadata: SetCardMetadataNode,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
function GroupSubCanvas({
|
|
38
|
+
initialNodes,
|
|
39
|
+
initialEdges,
|
|
40
|
+
onChange,
|
|
41
|
+
}: {
|
|
42
|
+
initialNodes: any[];
|
|
43
|
+
initialEdges: any[];
|
|
44
|
+
onChange: (nodes: any[], edges: any[]) => void;
|
|
45
|
+
}) {
|
|
46
|
+
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
|
47
|
+
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
|
48
|
+
const nodeTypes = useMemo(() => INNER_NODE_TYPE_MAP, []);
|
|
49
|
+
|
|
50
|
+
const onConnect = useCallback(
|
|
51
|
+
(connection: Connection) => {
|
|
52
|
+
const edge = {
|
|
53
|
+
...connection,
|
|
54
|
+
type: 'smoothstep',
|
|
55
|
+
animated: true,
|
|
56
|
+
style: { stroke: 'var(--accent)', strokeWidth: 2 },
|
|
57
|
+
markerEnd: { type: 'arrowclosed' as const, color: 'var(--accent)' },
|
|
58
|
+
};
|
|
59
|
+
setEdges((eds) => addEdge(edge, eds));
|
|
60
|
+
},
|
|
61
|
+
[setEdges],
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
// Notify parent when nodes/edges change
|
|
65
|
+
React.useEffect(() => {
|
|
66
|
+
onChange(nodes, edges);
|
|
67
|
+
}, [nodes, edges, onChange]);
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<ReactFlow
|
|
71
|
+
nodes={nodes}
|
|
72
|
+
edges={edges}
|
|
73
|
+
onNodesChange={onNodesChange}
|
|
74
|
+
onEdgesChange={onEdgesChange}
|
|
75
|
+
onConnect={onConnect}
|
|
76
|
+
nodeTypes={nodeTypes}
|
|
77
|
+
snapToGrid
|
|
78
|
+
snapGrid={[8, 8]}
|
|
79
|
+
fitView
|
|
80
|
+
deleteKeyCode={['Backspace', 'Delete']}
|
|
81
|
+
defaultEdgeOptions={{
|
|
82
|
+
type: 'smoothstep',
|
|
83
|
+
animated: true,
|
|
84
|
+
style: { stroke: 'var(--accent)', strokeWidth: 1.5 },
|
|
85
|
+
markerEnd: { type: 'arrowclosed' as const, color: 'var(--accent)' },
|
|
86
|
+
}}
|
|
87
|
+
proOptions={{ hideAttribution: true }}
|
|
88
|
+
className="group-sub-canvas"
|
|
89
|
+
>
|
|
90
|
+
<Background variant={BackgroundVariant.Dots} gap={8} size={0.5} color="var(--border-color)" />
|
|
91
|
+
<MiniMap
|
|
92
|
+
nodeStrokeColor="var(--accent)"
|
|
93
|
+
nodeColor="var(--bg-tertiary)"
|
|
94
|
+
maskColor="rgba(0,0,0,0.3)"
|
|
95
|
+
className="!bg-surface !border !border-edge !rounded !w-24 !h-16"
|
|
96
|
+
pannable
|
|
97
|
+
zoomable={false}
|
|
98
|
+
/>
|
|
99
|
+
</ReactFlow>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function GroupNode({ data, selected }: NodeProps) {
|
|
104
|
+
const [expanded, setExpanded] = useState(false);
|
|
105
|
+
const groupData = data as any;
|
|
106
|
+
const groupName = groupData.groupName || 'Group';
|
|
107
|
+
const internalNodes = groupData.internalNodes || [];
|
|
108
|
+
const internalEdges = groupData.internalEdges || [];
|
|
109
|
+
const inputHandles: string[] = groupData.inputHandles || ['input'];
|
|
110
|
+
const outputHandles: string[] = groupData.outputHandles || ['output'];
|
|
111
|
+
|
|
112
|
+
const handleToggle = useCallback(() => {
|
|
113
|
+
setExpanded((prev) => !prev);
|
|
114
|
+
}, []);
|
|
115
|
+
|
|
116
|
+
const handleInternalChange = useCallback(
|
|
117
|
+
(nodes: any[], edges: any[]) => {
|
|
118
|
+
// Update the group instance data (not the saved group definition)
|
|
119
|
+
groupData.internalNodes = nodes;
|
|
120
|
+
groupData.internalEdges = edges;
|
|
121
|
+
},
|
|
122
|
+
[groupData],
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<div
|
|
127
|
+
className={`rounded-lg border bg-surface-raised text-content ${
|
|
128
|
+
selected ? 'border-accent shadow-[0_0_0_1px_var(--accent)]' : 'border-edge'
|
|
129
|
+
} ${expanded ? 'min-w-[500px] min-h-[350px]' : 'min-w-[160px]'}`}
|
|
130
|
+
>
|
|
131
|
+
{/* Input handles */}
|
|
132
|
+
{inputHandles.map((handleId, i) => (
|
|
133
|
+
<Handle
|
|
134
|
+
key={`in-${handleId}`}
|
|
135
|
+
type="target"
|
|
136
|
+
position={Position.Top}
|
|
137
|
+
id={handleId}
|
|
138
|
+
className="!w-3 !h-3 !bg-accent !border-2 !border-surface"
|
|
139
|
+
style={inputHandles.length > 1 ? { left: `${((i + 1) / (inputHandles.length + 1)) * 100}%` } : undefined}
|
|
140
|
+
/>
|
|
141
|
+
))}
|
|
142
|
+
|
|
143
|
+
{/* Header */}
|
|
144
|
+
<div className="flex items-center gap-2 px-3 py-2 border-b border-edge">
|
|
145
|
+
<Layers size={14} className="text-emerald-400 shrink-0" />
|
|
146
|
+
<span className="text-[12px] font-semibold flex-1 truncate">{groupName}</span>
|
|
147
|
+
<button
|
|
148
|
+
onClick={handleToggle}
|
|
149
|
+
className="p-0.5 rounded hover:bg-surface transition-colors"
|
|
150
|
+
title={expanded ? 'Collapse group' : 'Expand group'}
|
|
151
|
+
>
|
|
152
|
+
{expanded ? <Minimize2 size={12} /> : <Maximize2 size={12} />}
|
|
153
|
+
</button>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
{/* Collapsed state — simple summary */}
|
|
157
|
+
{!expanded && (
|
|
158
|
+
<div className="px-3 py-2 text-[10px] text-content-muted">
|
|
159
|
+
{internalNodes.length} nodes
|
|
160
|
+
</div>
|
|
161
|
+
)}
|
|
162
|
+
|
|
163
|
+
{/* Expanded state — nested ReactFlow sub-canvas */}
|
|
164
|
+
{expanded && (
|
|
165
|
+
<div className="w-[480px] h-[280px] p-1">
|
|
166
|
+
<ReactFlowProvider>
|
|
167
|
+
<GroupSubCanvas
|
|
168
|
+
initialNodes={internalNodes}
|
|
169
|
+
initialEdges={internalEdges}
|
|
170
|
+
onChange={handleInternalChange}
|
|
171
|
+
/>
|
|
172
|
+
</ReactFlowProvider>
|
|
173
|
+
</div>
|
|
174
|
+
)}
|
|
175
|
+
|
|
176
|
+
{/* Output handles */}
|
|
177
|
+
{outputHandles.map((handleId, i) => (
|
|
178
|
+
<Handle
|
|
179
|
+
key={`out-${handleId}`}
|
|
180
|
+
type="source"
|
|
181
|
+
position={Position.Bottom}
|
|
182
|
+
id={handleId}
|
|
183
|
+
className="!w-3 !h-3 !bg-accent !border-2 !border-surface"
|
|
184
|
+
style={outputHandles.length > 1 ? { left: `${((i + 1) / (outputHandles.length + 1)) * 100}%` } : undefined}
|
|
185
|
+
/>
|
|
186
|
+
))}
|
|
187
|
+
</div>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Play, Square, ArrowRightLeft, Bot, GitBranch, RefreshCw, Tag, Layers, Trash2,
|
|
4
|
+
} from 'lucide-react';
|
|
5
|
+
import { NODE_PALETTE_ITEMS } from './workflow_types';
|
|
6
|
+
import type { WorkflowNodeType } from './workflow_types';
|
|
7
|
+
import { apiClient } from '../../api/client';
|
|
8
|
+
|
|
9
|
+
const NODE_ICONS: Record<string, React.ReactNode> = {
|
|
10
|
+
start: <Play size={14} className="text-accent" />,
|
|
11
|
+
end: <Square size={14} className="text-red-400" />,
|
|
12
|
+
transitionCard: <ArrowRightLeft size={14} className="text-blue-400" />,
|
|
13
|
+
runAgent: <Bot size={14} className="text-purple-400" />,
|
|
14
|
+
checkCardPosition: <GitBranch size={14} className="text-cyan-400" />,
|
|
15
|
+
checkCycleCount: <RefreshCw size={14} className="text-orange-400" />,
|
|
16
|
+
setCardMetadata: <Tag size={14} className="text-yellow-400" />,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
interface WorkflowGroup {
|
|
20
|
+
id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
projectId: string | null;
|
|
23
|
+
graphData: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface NodePaletteProps {
|
|
27
|
+
projectId?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function NodePalette({ projectId }: NodePaletteProps) {
|
|
31
|
+
const [groups, setGroups] = useState<WorkflowGroup[]>([]);
|
|
32
|
+
|
|
33
|
+
const fetchGroups = useCallback(async () => {
|
|
34
|
+
try {
|
|
35
|
+
const resp = await apiClient.fetchWorkflowGroups(projectId);
|
|
36
|
+
setGroups(resp.groups || []);
|
|
37
|
+
} catch {
|
|
38
|
+
// Silent fail — groups are optional
|
|
39
|
+
}
|
|
40
|
+
}, [projectId]);
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
fetchGroups();
|
|
44
|
+
}, [fetchGroups]);
|
|
45
|
+
|
|
46
|
+
const onDragStart = useCallback((event: React.DragEvent, nodeType: WorkflowNodeType) => {
|
|
47
|
+
event.dataTransfer.setData('application/reactflow', nodeType);
|
|
48
|
+
event.dataTransfer.effectAllowed = 'move';
|
|
49
|
+
}, []);
|
|
50
|
+
|
|
51
|
+
const onGroupDragStart = useCallback((event: React.DragEvent, group: WorkflowGroup) => {
|
|
52
|
+
event.dataTransfer.setData('application/workflow-group', JSON.stringify(group));
|
|
53
|
+
event.dataTransfer.effectAllowed = 'move';
|
|
54
|
+
}, []);
|
|
55
|
+
|
|
56
|
+
const handleDeleteGroup = useCallback(async (groupId: string) => {
|
|
57
|
+
try {
|
|
58
|
+
await apiClient.deleteWorkflowGroup(groupId);
|
|
59
|
+
setGroups((prev) => prev.filter((g) => g.id !== groupId));
|
|
60
|
+
} catch {
|
|
61
|
+
// Silent fail
|
|
62
|
+
}
|
|
63
|
+
}, []);
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<div className="space-y-4">
|
|
67
|
+
{/* Standard node types */}
|
|
68
|
+
<div className="space-y-1">
|
|
69
|
+
<div className="text-[11px] font-medium text-content-muted uppercase tracking-wider px-1 mb-2">Node Palette</div>
|
|
70
|
+
{NODE_PALETTE_ITEMS.map(item => (
|
|
71
|
+
<div
|
|
72
|
+
key={item.type}
|
|
73
|
+
draggable
|
|
74
|
+
onDragStart={(e) => onDragStart(e, item.type)}
|
|
75
|
+
className="flex items-center gap-2.5 px-2.5 py-2 rounded-md border border-edge bg-surface hover:bg-surface-raised hover:border-content/20 cursor-grab active:cursor-grabbing transition-all text-[12px]"
|
|
76
|
+
>
|
|
77
|
+
{NODE_ICONS[item.type]}
|
|
78
|
+
<div>
|
|
79
|
+
<div className="font-medium text-content">{item.label}</div>
|
|
80
|
+
<div className="text-[10px] text-content-muted">{item.description}</div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
))}
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
{/* Reusable Groups section */}
|
|
87
|
+
<div className="space-y-1">
|
|
88
|
+
<div className="text-[11px] font-medium text-content-muted uppercase tracking-wider px-1 mb-2">Groups</div>
|
|
89
|
+
{groups.length === 0 ? (
|
|
90
|
+
<div className="px-2.5 py-2 text-[10px] text-content-muted italic">
|
|
91
|
+
No groups yet. Select nodes on the canvas, right-click, and choose "Save as Reusable Group".
|
|
92
|
+
</div>
|
|
93
|
+
) : (
|
|
94
|
+
groups.map((group) => (
|
|
95
|
+
<div
|
|
96
|
+
key={group.id}
|
|
97
|
+
draggable
|
|
98
|
+
onDragStart={(e) => onGroupDragStart(e, group)}
|
|
99
|
+
className="flex items-center gap-2.5 px-2.5 py-2 rounded-md border border-edge bg-surface hover:bg-surface-raised hover:border-content/20 cursor-grab active:cursor-grabbing transition-all text-[12px] group/item"
|
|
100
|
+
>
|
|
101
|
+
<Layers size={14} className="text-emerald-400 shrink-0" />
|
|
102
|
+
<div className="flex-1 min-w-0">
|
|
103
|
+
<div className="font-medium text-content truncate">{group.name}</div>
|
|
104
|
+
<div className="text-[10px] text-content-muted">Reusable group</div>
|
|
105
|
+
</div>
|
|
106
|
+
<button
|
|
107
|
+
onClick={(e) => {
|
|
108
|
+
e.stopPropagation();
|
|
109
|
+
e.preventDefault();
|
|
110
|
+
handleDeleteGroup(group.id);
|
|
111
|
+
}}
|
|
112
|
+
className="opacity-0 group-hover/item:opacity-100 p-0.5 rounded hover:bg-error/10 text-content-muted hover:text-error transition-all"
|
|
113
|
+
title="Delete group"
|
|
114
|
+
>
|
|
115
|
+
<Trash2 size={10} />
|
|
116
|
+
</button>
|
|
117
|
+
</div>
|
|
118
|
+
))
|
|
119
|
+
)}
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import { Handle, Position, useReactFlow } from '@xyflow/react';
|
|
3
|
+
import type { NodeProps } from '@xyflow/react';
|
|
4
|
+
import { Bot } from 'lucide-react';
|
|
5
|
+
import { apiClient } from '../../api/client';
|
|
6
|
+
|
|
7
|
+
interface AgentOption {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function RunAgentNode({ id, data, selected }: NodeProps) {
|
|
13
|
+
const { updateNodeData } = useReactFlow();
|
|
14
|
+
const agentId = (data.agentId as string) || '';
|
|
15
|
+
const [agents, setAgents] = useState<AgentOption[]>([]);
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
apiClient.fetchAgents('global').then(resp => {
|
|
19
|
+
setAgents(resp.agents.map((a: AgentOption) => ({ id: a.id, name: a.name })));
|
|
20
|
+
}).catch(() => {});
|
|
21
|
+
}, []);
|
|
22
|
+
|
|
23
|
+
const handleChange = useCallback((e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
24
|
+
const selectedId = e.target.value;
|
|
25
|
+
const agent = agents.find(a => a.id === selectedId);
|
|
26
|
+
updateNodeData(id, { agentId: selectedId, agentName: agent?.name || '' });
|
|
27
|
+
}, [id, updateNodeData, agents]);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div className={`px-4 py-3 rounded-lg border bg-surface-raised text-content min-w-[180px] ${
|
|
31
|
+
selected ? 'border-accent shadow-[0_0_0_1px_var(--accent)]' : 'border-edge'
|
|
32
|
+
}`}>
|
|
33
|
+
<Handle type="target" position={Position.Top} className="!w-3 !h-3 !bg-accent !border-2 !border-surface" />
|
|
34
|
+
<div className="flex items-center gap-2 mb-2">
|
|
35
|
+
<Bot size={14} className="text-purple-400" />
|
|
36
|
+
<span className="text-[12px] font-semibold">Run Agent</span>
|
|
37
|
+
</div>
|
|
38
|
+
<select
|
|
39
|
+
value={agentId}
|
|
40
|
+
onChange={handleChange}
|
|
41
|
+
className="w-full px-2 py-1 rounded border border-edge bg-surface text-[11px] text-content appearance-none"
|
|
42
|
+
>
|
|
43
|
+
<option value="">Select agent...</option>
|
|
44
|
+
{agents.map(a => (
|
|
45
|
+
<option key={a.id} value={a.id}>{a.name}</option>
|
|
46
|
+
))}
|
|
47
|
+
</select>
|
|
48
|
+
<Handle type="source" position={Position.Bottom} className="!w-3 !h-3 !bg-accent !border-2 !border-surface" />
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import React, { useCallback } from 'react';
|
|
2
|
+
import { Handle, Position, useReactFlow } from '@xyflow/react';
|
|
3
|
+
import type { NodeProps } from '@xyflow/react';
|
|
4
|
+
import { Tag } from 'lucide-react';
|
|
5
|
+
|
|
6
|
+
export function SetCardMetadataNode({ id, data, selected }: NodeProps) {
|
|
7
|
+
const { updateNodeData } = useReactFlow();
|
|
8
|
+
const key = (data.key as string) || '';
|
|
9
|
+
const value = (data.value as string) || '';
|
|
10
|
+
|
|
11
|
+
const handleKeyChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
|
12
|
+
updateNodeData(id, { key: e.target.value });
|
|
13
|
+
}, [id, updateNodeData]);
|
|
14
|
+
|
|
15
|
+
const handleValueChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
|
16
|
+
updateNodeData(id, { value: e.target.value });
|
|
17
|
+
}, [id, updateNodeData]);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div className={`px-4 py-3 rounded-lg border bg-surface-raised text-content min-w-[180px] ${
|
|
21
|
+
selected ? 'border-accent shadow-[0_0_0_1px_var(--accent)]' : 'border-edge'
|
|
22
|
+
}`}>
|
|
23
|
+
<Handle type="target" position={Position.Top} className="!w-3 !h-3 !bg-accent !border-2 !border-surface" />
|
|
24
|
+
<div className="flex items-center gap-2 mb-2">
|
|
25
|
+
<Tag size={14} className="text-yellow-400" />
|
|
26
|
+
<span className="text-[12px] font-semibold">Set Card Metadata</span>
|
|
27
|
+
</div>
|
|
28
|
+
<div className="space-y-1.5">
|
|
29
|
+
<div>
|
|
30
|
+
<label className="text-[10px] text-content-muted uppercase tracking-wider">Key</label>
|
|
31
|
+
<input
|
|
32
|
+
type="text"
|
|
33
|
+
value={key}
|
|
34
|
+
onChange={handleKeyChange}
|
|
35
|
+
placeholder="e.g. dev_blocked"
|
|
36
|
+
className="w-full px-2 py-1 rounded border border-edge bg-surface text-[11px] text-content"
|
|
37
|
+
/>
|
|
38
|
+
</div>
|
|
39
|
+
<div>
|
|
40
|
+
<label className="text-[10px] text-content-muted uppercase tracking-wider">Value</label>
|
|
41
|
+
<input
|
|
42
|
+
type="text"
|
|
43
|
+
value={value}
|
|
44
|
+
onChange={handleValueChange}
|
|
45
|
+
placeholder="e.g. true"
|
|
46
|
+
className="w-full px-2 py-1 rounded border border-edge bg-surface text-[11px] text-content"
|
|
47
|
+
/>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
<Handle type="source" position={Position.Bottom} className="!w-3 !h-3 !bg-accent !border-2 !border-surface" />
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
}
|
package/templates/assistkick-product-system/packages/frontend/src/components/workflow/StartNode.tsx
ADDED
|
@@ -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
|
+
}
|