@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.
- 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 +61 -6
- 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 +158 -0
- package/templates/assistkick-product-system/packages/backend/src/server.ts +60 -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 +43 -77
- 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 +245 -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 +458 -18
- 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/IterationCommentModal.tsx +80 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/KanbanView.tsx +263 -167
- 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/GenerateTTSNode.tsx +52 -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/RebuildBundleNode.tsx +20 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/RenderVideoNode.tsx +72 -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 +341 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowMonitorModal.tsx +643 -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 +246 -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 +136 -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/0014_nifty_punisher.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/0014_snapshot.json +1545 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/_journal.json +77 -0
- package/templates/assistkick-product-system/packages/shared/db/schema.ts +114 -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 +1999 -0
- package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.ts +1437 -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 +181 -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
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React, { useCallback } from 'react';
|
|
2
|
+
import { Handle, Position, useReactFlow } from '@xyflow/react';
|
|
3
|
+
import type { NodeProps } from '@xyflow/react';
|
|
4
|
+
import { Volume2 } from 'lucide-react';
|
|
5
|
+
|
|
6
|
+
export function GenerateTTSNode({ id, data, selected }: NodeProps) {
|
|
7
|
+
const { updateNodeData } = useReactFlow();
|
|
8
|
+
const scriptPath = (data.scriptPath as string) || 'script.md';
|
|
9
|
+
const force = (data.force as boolean) || false;
|
|
10
|
+
|
|
11
|
+
const handleScriptPathChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
|
12
|
+
updateNodeData(id, { scriptPath: e.target.value });
|
|
13
|
+
}, [id, updateNodeData]);
|
|
14
|
+
|
|
15
|
+
const handleForceChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
|
16
|
+
updateNodeData(id, { force: e.target.checked });
|
|
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
|
+
<Volume2 size={14} className="text-emerald-400" />
|
|
26
|
+
<span className="text-[12px] font-semibold">Generate TTS</span>
|
|
27
|
+
</div>
|
|
28
|
+
<div className="space-y-1.5">
|
|
29
|
+
<div>
|
|
30
|
+
<label className="text-[10px] text-content-muted uppercase tracking-wider">Script</label>
|
|
31
|
+
<input
|
|
32
|
+
type="text"
|
|
33
|
+
value={scriptPath}
|
|
34
|
+
onChange={handleScriptPathChange}
|
|
35
|
+
className="w-full px-2 py-1 rounded border border-edge bg-surface text-[11px] text-content"
|
|
36
|
+
placeholder="script.md"
|
|
37
|
+
/>
|
|
38
|
+
</div>
|
|
39
|
+
<label className="flex items-center gap-1.5 text-[10px] text-content-muted cursor-pointer">
|
|
40
|
+
<input
|
|
41
|
+
type="checkbox"
|
|
42
|
+
checked={force}
|
|
43
|
+
onChange={handleForceChange}
|
|
44
|
+
className="rounded"
|
|
45
|
+
/>
|
|
46
|
+
Force regeneration
|
|
47
|
+
</label>
|
|
48
|
+
</div>
|
|
49
|
+
<Handle type="source" position={Position.Bottom} className="!w-3 !h-3 !bg-accent !border-2 !border-surface" />
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
}
|
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,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Handle, Position } from '@xyflow/react';
|
|
3
|
+
import type { NodeProps } from '@xyflow/react';
|
|
4
|
+
import { Package } from 'lucide-react';
|
|
5
|
+
|
|
6
|
+
export function RebuildBundleNode({ id, selected }: NodeProps) {
|
|
7
|
+
return (
|
|
8
|
+
<div className={`px-4 py-3 rounded-lg border bg-surface-raised text-content min-w-[180px] ${
|
|
9
|
+
selected ? 'border-accent shadow-[0_0_0_1px_var(--accent)]' : 'border-edge'
|
|
10
|
+
}`}>
|
|
11
|
+
<Handle type="target" position={Position.Top} className="!w-3 !h-3 !bg-accent !border-2 !border-surface" />
|
|
12
|
+
<div className="flex items-center gap-2">
|
|
13
|
+
<Package size={14} className="text-orange-400" />
|
|
14
|
+
<span className="text-[12px] font-semibold">Rebuild Bundle</span>
|
|
15
|
+
</div>
|
|
16
|
+
<div className="text-[10px] text-content-muted mt-1">Remotion webpack build</div>
|
|
17
|
+
<Handle type="source" position={Position.Bottom} className="!w-3 !h-3 !bg-accent !border-2 !border-surface" />
|
|
18
|
+
</div>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import React, { useCallback } from 'react';
|
|
2
|
+
import { Handle, Position, useReactFlow } from '@xyflow/react';
|
|
3
|
+
import type { NodeProps } from '@xyflow/react';
|
|
4
|
+
import { Film } from 'lucide-react';
|
|
5
|
+
|
|
6
|
+
export function RenderVideoNode({ id, data, selected }: NodeProps) {
|
|
7
|
+
const { updateNodeData } = useReactFlow();
|
|
8
|
+
const compositionId = (data.compositionId as string) || '';
|
|
9
|
+
const resolution = (data.resolution as string) || '1920x1080';
|
|
10
|
+
const fileOutputPrefix = (data.fileOutputPrefix as string) || '';
|
|
11
|
+
|
|
12
|
+
const handleCompositionChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
|
13
|
+
updateNodeData(id, { compositionId: e.target.value });
|
|
14
|
+
}, [id, updateNodeData]);
|
|
15
|
+
|
|
16
|
+
const handleResolutionChange = useCallback((e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
17
|
+
updateNodeData(id, { resolution: e.target.value });
|
|
18
|
+
}, [id, updateNodeData]);
|
|
19
|
+
|
|
20
|
+
const handlePrefixChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
|
21
|
+
updateNodeData(id, { fileOutputPrefix: e.target.value });
|
|
22
|
+
}, [id, updateNodeData]);
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div className={`px-4 py-3 rounded-lg border bg-surface-raised text-content min-w-[200px] ${
|
|
26
|
+
selected ? 'border-accent shadow-[0_0_0_1px_var(--accent)]' : 'border-edge'
|
|
27
|
+
}`}>
|
|
28
|
+
<Handle type="target" position={Position.Top} className="!w-3 !h-3 !bg-accent !border-2 !border-surface" />
|
|
29
|
+
<div className="flex items-center gap-2 mb-2">
|
|
30
|
+
<Film size={14} className="text-rose-400" />
|
|
31
|
+
<span className="text-[12px] font-semibold">Render Video</span>
|
|
32
|
+
</div>
|
|
33
|
+
<div className="space-y-1.5">
|
|
34
|
+
<div>
|
|
35
|
+
<label className="text-[10px] text-content-muted uppercase tracking-wider">Composition</label>
|
|
36
|
+
<input
|
|
37
|
+
type="text"
|
|
38
|
+
value={compositionId}
|
|
39
|
+
onChange={handleCompositionChange}
|
|
40
|
+
className="w-full px-2 py-1 rounded border border-edge bg-surface text-[11px] text-content"
|
|
41
|
+
placeholder="auto from context"
|
|
42
|
+
/>
|
|
43
|
+
</div>
|
|
44
|
+
<div>
|
|
45
|
+
<label className="text-[10px] text-content-muted uppercase tracking-wider">Resolution</label>
|
|
46
|
+
<select
|
|
47
|
+
value={resolution}
|
|
48
|
+
onChange={handleResolutionChange}
|
|
49
|
+
className="w-full px-2 py-1 rounded border border-edge bg-surface text-[11px] text-content appearance-none"
|
|
50
|
+
>
|
|
51
|
+
<option value="1920x1080">1920x1080 (16:9)</option>
|
|
52
|
+
<option value="1080x1920">1080x1920 (9:16)</option>
|
|
53
|
+
<option value="1280x720">1280x720 (16:9)</option>
|
|
54
|
+
<option value="720x1280">720x1280 (9:16)</option>
|
|
55
|
+
<option value="1080x1080">1080x1080 (1:1)</option>
|
|
56
|
+
</select>
|
|
57
|
+
</div>
|
|
58
|
+
<div>
|
|
59
|
+
<label className="text-[10px] text-content-muted uppercase tracking-wider">Output prefix</label>
|
|
60
|
+
<input
|
|
61
|
+
type="text"
|
|
62
|
+
value={fileOutputPrefix}
|
|
63
|
+
onChange={handlePrefixChange}
|
|
64
|
+
className="w-full px-2 py-1 rounded border border-edge bg-surface text-[11px] text-content"
|
|
65
|
+
placeholder="auto"
|
|
66
|
+
/>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
<Handle type="source" position={Position.Bottom} className="!w-3 !h-3 !bg-accent !border-2 !border-surface" />
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
@@ -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
|
+
}
|