@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
|
@@ -1,88 +1,204 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Workflow execution routes — start/resume/status for workflow executions + orchestrator.
|
|
3
|
+
* Replaces the old pipeline routes with WorkflowEngine-based endpoints.
|
|
3
4
|
*/
|
|
4
5
|
|
|
5
6
|
import { Router } from 'express';
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
router
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
7
|
+
import type { WorkflowEngine } from '@assistkick/shared/lib/workflow_engine.js';
|
|
8
|
+
import type { WorkflowOrchestrator } from '@assistkick/shared/lib/workflow_orchestrator.js';
|
|
9
|
+
import type { WorkflowService } from '../services/workflow_service.js';
|
|
10
|
+
|
|
11
|
+
interface WorkflowExecutionRoutesDeps {
|
|
12
|
+
workflowEngine: WorkflowEngine;
|
|
13
|
+
orchestrator: WorkflowOrchestrator;
|
|
14
|
+
workflowService: WorkflowService;
|
|
15
|
+
log: (tag: string, ...args: any[]) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const createWorkflowExecutionRoutes = ({
|
|
19
|
+
workflowEngine,
|
|
20
|
+
orchestrator,
|
|
21
|
+
workflowService,
|
|
22
|
+
log,
|
|
23
|
+
}: WorkflowExecutionRoutesDeps): Router => {
|
|
24
|
+
const router: Router = Router();
|
|
25
|
+
|
|
26
|
+
// POST /api/kanban/:id/workflow — start a workflow execution for a feature
|
|
27
|
+
router.post('/:id/workflow', async (req, res) => {
|
|
28
|
+
const featureId = req.params.id;
|
|
29
|
+
const { workflowId, projectId } = req.body || {};
|
|
30
|
+
log('WORKFLOW', `POST /api/kanban/${featureId}/workflow workflowId=${workflowId || 'default'}`);
|
|
31
|
+
|
|
32
|
+
if (!projectId) {
|
|
33
|
+
res.status(400).json({ error: 'projectId is required' });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
// Resolve workflow ID: use provided workflowId, or find the default
|
|
39
|
+
let resolvedWorkflowId = workflowId;
|
|
40
|
+
if (!resolvedWorkflowId) {
|
|
41
|
+
const defaultWorkflow = await workflowService.resolveDefault(projectId);
|
|
42
|
+
if (!defaultWorkflow) {
|
|
43
|
+
res.status(400).json({ error: 'No default workflow found. Please specify a workflowId or set a default workflow.' });
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
resolvedWorkflowId = defaultWorkflow.id;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const result = await workflowEngine.start(featureId, resolvedWorkflowId, projectId);
|
|
50
|
+
res.json({ started: true, feature_id: featureId, executionId: result.executionId });
|
|
51
|
+
} catch (err: any) {
|
|
52
|
+
log('WORKFLOW', `START ERROR: ${err.message}`);
|
|
53
|
+
res.status(500).json({ error: err.message });
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// GET /api/kanban/:id/workflow-status — get workflow execution state for a feature
|
|
58
|
+
router.get('/:id/workflow-status', async (req, res) => {
|
|
59
|
+
const featureId = req.params.id;
|
|
60
|
+
try {
|
|
61
|
+
const status = await workflowEngine.getStatus(featureId);
|
|
62
|
+
res.json(status);
|
|
63
|
+
} catch (err: any) {
|
|
64
|
+
log('WORKFLOW', `STATUS ERROR: ${err.message}`);
|
|
65
|
+
res.status(500).json({ error: err.message });
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// POST /api/kanban/:id/workflow/resume — resume a failed/interrupted workflow
|
|
70
|
+
router.post('/:id/workflow/resume', async (req, res) => {
|
|
71
|
+
const featureId = req.params.id;
|
|
72
|
+
log('WORKFLOW', `POST /api/kanban/${featureId}/workflow/resume`);
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
// Find the most recent resumable execution for this feature
|
|
76
|
+
const executionId = await workflowEngine.findResumableExecution(featureId);
|
|
77
|
+
if (!executionId) {
|
|
78
|
+
res.status(404).json({ error: `No resumable workflow execution found for feature ${featureId}` });
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
await workflowEngine.resume(executionId);
|
|
83
|
+
res.json({ resumed: true, feature_id: featureId, executionId });
|
|
84
|
+
} catch (err: any) {
|
|
85
|
+
log('WORKFLOW', `RESUME ERROR: ${err.message}`);
|
|
86
|
+
res.status(500).json({ error: err.message });
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// POST /api/kanban/:id/workflow/force-next — skip current node and advance to next
|
|
91
|
+
router.post('/:id/workflow/force-next', async (req, res) => {
|
|
92
|
+
const featureId = req.params.id;
|
|
93
|
+
const { nodeId } = req.body || {};
|
|
94
|
+
log('WORKFLOW', `POST /api/kanban/${featureId}/workflow/force-next nodeId=${nodeId}`);
|
|
95
|
+
|
|
96
|
+
if (!nodeId) {
|
|
97
|
+
res.status(400).json({ error: 'nodeId is required' });
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
await workflowEngine.forceNextNode(featureId, nodeId);
|
|
103
|
+
res.json({ success: true, feature_id: featureId, skippedNodeId: nodeId });
|
|
104
|
+
} catch (err: any) {
|
|
105
|
+
log('WORKFLOW', `FORCE-NEXT ERROR: ${err.message}`);
|
|
106
|
+
res.status(500).json({ error: err.message });
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// POST /api/kanban/:id/workflow/restart-node — restart the current node
|
|
111
|
+
router.post('/:id/workflow/restart-node', async (req, res) => {
|
|
112
|
+
const featureId = req.params.id;
|
|
113
|
+
const { nodeId } = req.body || {};
|
|
114
|
+
log('WORKFLOW', `POST /api/kanban/${featureId}/workflow/restart-node nodeId=${nodeId}`);
|
|
115
|
+
|
|
116
|
+
if (!nodeId) {
|
|
117
|
+
res.status(400).json({ error: 'nodeId is required' });
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
await workflowEngine.restartNode(featureId, nodeId);
|
|
123
|
+
res.json({ success: true, feature_id: featureId, restartedNodeId: nodeId });
|
|
124
|
+
} catch (err: any) {
|
|
125
|
+
log('WORKFLOW', `RESTART-NODE ERROR: ${err.message}`);
|
|
126
|
+
res.status(500).json({ error: err.message });
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// GET /api/kanban/:id/workflow-monitor — get full execution monitor data for a feature
|
|
131
|
+
router.get('/:id/workflow-monitor', async (req, res) => {
|
|
132
|
+
const featureId = req.params.id;
|
|
133
|
+
try {
|
|
134
|
+
const data = await workflowEngine.getMonitorData(featureId);
|
|
135
|
+
if (!data) {
|
|
136
|
+
res.json({ monitor: null });
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
res.json({ monitor: data });
|
|
140
|
+
} catch (err: any) {
|
|
141
|
+
log('WORKFLOW', `MONITOR ERROR: ${err.message}`);
|
|
142
|
+
res.status(500).json({ error: err.message });
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// GET /api/kanban/:id/workflow-metrics — get per-agent KPI metrics for a feature's latest execution
|
|
147
|
+
router.get('/:id/workflow-metrics', async (req, res) => {
|
|
148
|
+
const featureId = req.params.id;
|
|
149
|
+
try {
|
|
150
|
+
const status = await workflowEngine.getStatus(featureId);
|
|
151
|
+
const executionId = status.executionId as string | undefined;
|
|
152
|
+
if (!executionId) {
|
|
153
|
+
res.json({ metrics: [] });
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const metrics = await workflowEngine.getExecutionMetrics(executionId);
|
|
157
|
+
res.json({ metrics });
|
|
158
|
+
} catch (err: any) {
|
|
159
|
+
log('WORKFLOW', `METRICS ERROR: ${err.message}`);
|
|
160
|
+
res.status(500).json({ error: err.message });
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// --- Orchestrator (Play All) endpoints ---
|
|
165
|
+
|
|
166
|
+
// POST /api/pipeline/play-all
|
|
167
|
+
router.post('/play-all', async (req, res) => {
|
|
168
|
+
const projectId = req.query.project_id as string | undefined;
|
|
169
|
+
const epicId = req.query.epic_id as string | undefined;
|
|
170
|
+
if (!projectId) {
|
|
171
|
+
res.status(400).json({ error: 'project_id query parameter is required' });
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
const result = await orchestrator.startPlayAll(projectId, epicId);
|
|
176
|
+
res.status(result.status).json(
|
|
177
|
+
result.error ? { error: result.error } : { started: result.started },
|
|
178
|
+
);
|
|
179
|
+
} catch (err: any) {
|
|
180
|
+
log('ORCHESTRATOR', `UNEXPECTED ERROR: ${err.message}`);
|
|
181
|
+
res.status(500).json({ error: err.message });
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// POST /api/pipeline/stop-all
|
|
186
|
+
router.post('/stop-all', (_req, res) => {
|
|
187
|
+
try {
|
|
188
|
+
const result = orchestrator.stopPlayAll();
|
|
189
|
+
res.status(result.status).json(
|
|
190
|
+
result.error ? { error: result.error } : { stopped: result.stopped },
|
|
191
|
+
);
|
|
192
|
+
} catch (err: any) {
|
|
193
|
+
log('ORCHESTRATOR', `UNEXPECTED ERROR: ${err.message}`);
|
|
194
|
+
res.status(500).json({ error: err.message });
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// GET /api/pipeline/orchestrator-status
|
|
199
|
+
router.get('/orchestrator-status', (_req, res) => {
|
|
200
|
+
res.json(orchestrator.getStatus());
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
return router;
|
|
204
|
+
};
|
|
@@ -33,16 +33,19 @@ export const createProjectRoutes = ({ projectService, log }: ProjectRoutesDeps):
|
|
|
33
33
|
|
|
34
34
|
// POST /api/projects — create a new project
|
|
35
35
|
router.post('/', async (req, res) => {
|
|
36
|
-
const { name } = req.body;
|
|
37
|
-
log('PROJECTS', `POST /api/projects name="${name}"`);
|
|
36
|
+
const { name, type } = req.body;
|
|
37
|
+
log('PROJECTS', `POST /api/projects name="${name}" type="${type || 'software'}"`);
|
|
38
38
|
|
|
39
39
|
if (!name || typeof name !== 'string' || !name.trim()) {
|
|
40
40
|
res.status(400).json({ error: 'Project name is required' });
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
const validTypes = ['software', 'video'];
|
|
45
|
+
const projectType = type && validTypes.includes(type) ? type : 'software';
|
|
46
|
+
|
|
44
47
|
try {
|
|
45
|
-
const project = await projectService.create(name.trim());
|
|
48
|
+
const project = await projectService.create(name.trim(), projectType);
|
|
46
49
|
res.status(201).json({ project });
|
|
47
50
|
} catch (err: any) {
|
|
48
51
|
log('PROJECTS', `Create project failed: ${err.message}`);
|
|
@@ -8,7 +8,12 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { Router } from 'express';
|
|
11
|
+
import express from 'express';
|
|
11
12
|
import type { Request, Response } from 'express';
|
|
13
|
+
import { writeFile, mkdir } from 'node:fs/promises';
|
|
14
|
+
import { tmpdir } from 'node:os';
|
|
15
|
+
import { join, extname } from 'node:path';
|
|
16
|
+
import { randomBytes } from 'node:crypto';
|
|
12
17
|
import type { PtySessionManager } from '../services/pty_session_manager.js';
|
|
13
18
|
|
|
14
19
|
interface TerminalRoutesDeps {
|
|
@@ -31,13 +36,13 @@ export const createTerminalRoutes = ({ ptyManager, log }: TerminalRoutesDeps): R
|
|
|
31
36
|
router.use(requireAdmin as any);
|
|
32
37
|
|
|
33
38
|
// GET /api/terminal/sessions
|
|
34
|
-
router.get('/sessions', (_req, res) => {
|
|
35
|
-
const sessions = ptyManager.listSessions();
|
|
39
|
+
router.get('/sessions', async (_req, res) => {
|
|
40
|
+
const sessions = await ptyManager.listSessions();
|
|
36
41
|
res.json({ sessions });
|
|
37
42
|
});
|
|
38
43
|
|
|
39
44
|
// POST /api/terminal/sessions
|
|
40
|
-
router.post('/sessions', (req, res) => {
|
|
45
|
+
router.post('/sessions', async (req, res) => {
|
|
41
46
|
const { projectId, projectName } = req.body;
|
|
42
47
|
|
|
43
48
|
if (!projectId || typeof projectId !== 'string') {
|
|
@@ -49,31 +54,62 @@ export const createTerminalRoutes = ({ ptyManager, log }: TerminalRoutesDeps): R
|
|
|
49
54
|
return;
|
|
50
55
|
}
|
|
51
56
|
|
|
52
|
-
const session = ptyManager.createSession(projectId.trim(), projectName.trim(), 80, 24);
|
|
57
|
+
const session = await ptyManager.createSession(projectId.trim(), projectName.trim(), 80, 24);
|
|
53
58
|
log('TERMINAL', `Created session "${session.name}" for project ${projectId}`);
|
|
54
|
-
res.status(201).json({
|
|
55
|
-
session: {
|
|
56
|
-
id: session.id,
|
|
57
|
-
name: session.name,
|
|
58
|
-
projectId: session.projectId,
|
|
59
|
-
projectName: session.projectName,
|
|
60
|
-
state: session.state,
|
|
61
|
-
createdAt: session.createdAt.toISOString(),
|
|
62
|
-
},
|
|
63
|
-
});
|
|
59
|
+
res.status(201).json({ session });
|
|
64
60
|
});
|
|
65
61
|
|
|
62
|
+
// POST /api/terminal/upload-image — accept a dropped image, save to temp, return path
|
|
63
|
+
const UPLOAD_DIR = join(tmpdir(), 'assistkick-terminal-uploads');
|
|
64
|
+
const ALLOWED_IMAGE_EXTS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.bmp']);
|
|
65
|
+
|
|
66
|
+
router.post(
|
|
67
|
+
'/upload-image',
|
|
68
|
+
express.raw({ limit: '10mb', type: 'application/octet-stream' }) as any,
|
|
69
|
+
async (req: Request, res: Response) => {
|
|
70
|
+
const filename = req.query.filename as string | undefined;
|
|
71
|
+
if (!filename) {
|
|
72
|
+
res.status(400).json({ error: 'filename query parameter is required' });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const ext = extname(filename).toLowerCase();
|
|
77
|
+
if (!ALLOWED_IMAGE_EXTS.has(ext)) {
|
|
78
|
+
res.status(400).json({ error: `Unsupported image type: ${ext}` });
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const body = req.body as Buffer;
|
|
83
|
+
if (!body || body.length === 0) {
|
|
84
|
+
res.status(400).json({ error: 'Empty file body' });
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
await mkdir(UPLOAD_DIR, { recursive: true });
|
|
90
|
+
const uniqueName = `${Date.now()}-${randomBytes(4).toString('hex')}${ext}`;
|
|
91
|
+
const filePath = join(UPLOAD_DIR, uniqueName);
|
|
92
|
+
await writeFile(filePath, body);
|
|
93
|
+
log('TERMINAL', `Uploaded image: ${filePath} (${body.length} bytes)`);
|
|
94
|
+
res.json({ path: filePath });
|
|
95
|
+
} catch (err: any) {
|
|
96
|
+
log('TERMINAL', `Image upload failed: ${err.message}`);
|
|
97
|
+
res.status(500).json({ error: 'Failed to save image' });
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
);
|
|
101
|
+
|
|
66
102
|
// DELETE /api/terminal/sessions/:id
|
|
67
|
-
router.delete('/sessions/:id', (req, res) => {
|
|
103
|
+
router.delete('/sessions/:id', async (req, res) => {
|
|
68
104
|
const { id } = req.params;
|
|
69
|
-
const session = ptyManager.getSession(id);
|
|
105
|
+
const session = await ptyManager.getSession(id);
|
|
70
106
|
|
|
71
107
|
if (!session) {
|
|
72
108
|
res.status(404).json({ error: 'Session not found' });
|
|
73
109
|
return;
|
|
74
110
|
}
|
|
75
111
|
|
|
76
|
-
ptyManager.destroySession(id);
|
|
112
|
+
await ptyManager.destroySession(id);
|
|
77
113
|
log('TERMINAL', `Killed session ${id}`);
|
|
78
114
|
res.json({ ok: true });
|
|
79
115
|
});
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Video routes — TTS audio generation, bundle management, composition listing, and rendering.
|
|
3
|
+
*
|
|
4
|
+
* POST /api/video/generate-tts — generate TTS audio from a video script
|
|
5
|
+
* GET /api/video/compositions — list available Remotion compositions
|
|
6
|
+
* GET /api/video/bundle/status — get bundle build status
|
|
7
|
+
* POST /api/video/bundle/rebuild — trigger a bundle rebuild
|
|
8
|
+
* GET /api/video/renders — list all renders for a project
|
|
9
|
+
* POST /api/video/render — start a video render
|
|
10
|
+
* GET /api/video/render/:id — get render status
|
|
11
|
+
* DELETE /api/video/render/:id — delete a render and its file
|
|
12
|
+
* GET /api/video/file/:renderId — serve rendered MP4 file
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { Router } from 'express';
|
|
16
|
+
import express from 'express';
|
|
17
|
+
import { existsSync } from 'node:fs';
|
|
18
|
+
import type { TtsService } from '../services/tts_service.js';
|
|
19
|
+
import type { BundleService } from '../services/bundle_service.js';
|
|
20
|
+
import type { VideoRenderService } from '../services/video_render_service.js';
|
|
21
|
+
|
|
22
|
+
interface VideoRoutesDeps {
|
|
23
|
+
ttsService: TtsService;
|
|
24
|
+
bundleService: BundleService;
|
|
25
|
+
videoRenderService: VideoRenderService;
|
|
26
|
+
log: (tag: string, ...args: any[]) => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const createVideoRoutes = ({ ttsService, bundleService, videoRenderService, log }: VideoRoutesDeps): Router => {
|
|
30
|
+
const router: Router = Router();
|
|
31
|
+
|
|
32
|
+
// POST /api/video/generate-tts
|
|
33
|
+
router.post('/generate-tts', async (req, res) => {
|
|
34
|
+
const { scriptPath, projectId, featureId, force, voiceId } = req.body;
|
|
35
|
+
log('VIDEO', `POST /api/video/generate-tts project=${projectId} feature=${featureId}`);
|
|
36
|
+
|
|
37
|
+
if (!scriptPath || typeof scriptPath !== 'string') {
|
|
38
|
+
res.status(400).json({ error: 'scriptPath is required' });
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!projectId || typeof projectId !== 'string') {
|
|
43
|
+
res.status(400).json({ error: 'projectId is required' });
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!featureId || typeof featureId !== 'string') {
|
|
48
|
+
res.status(400).json({ error: 'featureId is required' });
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!process.env.ELEVENLABS_API_KEY) {
|
|
53
|
+
res.status(500).json({ error: 'ELEVENLABS_API_KEY is not configured on the server.' });
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const result = await ttsService.generate({
|
|
59
|
+
scriptPath,
|
|
60
|
+
projectId,
|
|
61
|
+
featureId,
|
|
62
|
+
force: force === true,
|
|
63
|
+
voiceId: typeof voiceId === 'string' ? voiceId : undefined,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
res.json(result);
|
|
67
|
+
} catch (err: any) {
|
|
68
|
+
log('VIDEO', `TTS generation failed: ${err.message}`);
|
|
69
|
+
res.status(500).json({ error: err.message });
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// GET /api/video/compositions — list available Remotion compositions with metadata
|
|
74
|
+
router.get('/compositions', (_req, res) => {
|
|
75
|
+
log('VIDEO', 'GET /api/video/compositions');
|
|
76
|
+
try {
|
|
77
|
+
const compositions = bundleService.listCompositions();
|
|
78
|
+
res.json({ compositions });
|
|
79
|
+
} catch (err: any) {
|
|
80
|
+
log('VIDEO', `Failed to list compositions: ${err.message}`);
|
|
81
|
+
res.status(500).json({ error: 'Failed to list compositions' });
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// GET /api/video/bundle/status — get current bundle status
|
|
86
|
+
router.get('/bundle/status', (_req, res) => {
|
|
87
|
+
log('VIDEO', 'GET /api/video/bundle/status');
|
|
88
|
+
res.json(bundleService.getStatus());
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// POST /api/video/bundle/rebuild — trigger a bundle rebuild
|
|
92
|
+
router.post('/bundle/rebuild', async (_req, res) => {
|
|
93
|
+
log('VIDEO', 'POST /api/video/bundle/rebuild');
|
|
94
|
+
try {
|
|
95
|
+
const status = await bundleService.buildBundle();
|
|
96
|
+
res.json(status);
|
|
97
|
+
} catch (err: any) {
|
|
98
|
+
log('VIDEO', `Bundle rebuild failed: ${err.message}`);
|
|
99
|
+
res.status(500).json({ error: 'Bundle rebuild failed' });
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// GET /api/video/renders — list all renders for a project
|
|
104
|
+
router.get('/renders', async (req, res) => {
|
|
105
|
+
const projectId = req.query.projectId as string | undefined;
|
|
106
|
+
log('VIDEO', `GET /api/video/renders project=${projectId}`);
|
|
107
|
+
|
|
108
|
+
if (!projectId || typeof projectId !== 'string') {
|
|
109
|
+
res.status(400).json({ error: 'projectId query parameter is required' });
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const renders = await videoRenderService.listRenders(projectId);
|
|
115
|
+
res.json({ renders });
|
|
116
|
+
} catch (err: any) {
|
|
117
|
+
log('VIDEO', `Failed to list renders: ${err.message}`);
|
|
118
|
+
res.status(500).json({ error: 'Failed to list renders' });
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// POST /api/video/render — start a video render
|
|
123
|
+
router.post('/render', async (req, res) => {
|
|
124
|
+
const { compositionId, projectId, featureId, resolution, aspectRatio, fileOutputPrefix } = req.body;
|
|
125
|
+
log('VIDEO', `POST /api/video/render composition=${compositionId} project=${projectId} feature=${featureId}`);
|
|
126
|
+
|
|
127
|
+
if (!compositionId || typeof compositionId !== 'string') {
|
|
128
|
+
res.status(400).json({ error: 'compositionId is required' });
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!projectId || typeof projectId !== 'string') {
|
|
133
|
+
res.status(400).json({ error: 'projectId is required' });
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!featureId || typeof featureId !== 'string') {
|
|
138
|
+
res.status(400).json({ error: 'featureId is required' });
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const status = await videoRenderService.startRender({
|
|
144
|
+
compositionId,
|
|
145
|
+
projectId,
|
|
146
|
+
featureId,
|
|
147
|
+
resolution: typeof resolution === 'string' ? resolution : undefined,
|
|
148
|
+
aspectRatio: typeof aspectRatio === 'string' ? aspectRatio : undefined,
|
|
149
|
+
fileOutputPrefix: typeof fileOutputPrefix === 'string' ? fileOutputPrefix : undefined,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
res.json(status);
|
|
153
|
+
} catch (err: any) {
|
|
154
|
+
log('VIDEO', `Render start failed: ${err.message}`);
|
|
155
|
+
res.status(500).json({ error: err.message });
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// GET /api/video/render/:id — get render status
|
|
160
|
+
router.get('/render/:id', async (req, res) => {
|
|
161
|
+
const { id } = req.params;
|
|
162
|
+
log('VIDEO', `GET /api/video/render/${id}`);
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
const status = await videoRenderService.getStatus(id);
|
|
166
|
+
if (!status) {
|
|
167
|
+
res.status(404).json({ error: 'Render not found' });
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
res.json(status);
|
|
171
|
+
} catch (err: any) {
|
|
172
|
+
log('VIDEO', `Failed to get render status: ${err.message}`);
|
|
173
|
+
res.status(500).json({ error: err.message });
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// DELETE /api/video/render/:id — delete a render and its MP4 file
|
|
178
|
+
router.delete('/render/:id', async (req, res) => {
|
|
179
|
+
const { id } = req.params;
|
|
180
|
+
log('VIDEO', `DELETE /api/video/render/${id}`);
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
const deleted = await videoRenderService.deleteRender(id);
|
|
184
|
+
if (!deleted) {
|
|
185
|
+
res.status(404).json({ error: 'Render not found' });
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
res.json({ ok: true });
|
|
189
|
+
} catch (err: any) {
|
|
190
|
+
log('VIDEO', `Failed to delete render: ${err.message}`);
|
|
191
|
+
res.status(500).json({ error: err.message });
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// GET /api/video/file/:renderId — serve the rendered MP4 file
|
|
196
|
+
router.get('/file/:renderId', async (req, res) => {
|
|
197
|
+
const { renderId } = req.params;
|
|
198
|
+
log('VIDEO', `GET /api/video/file/${renderId}`);
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
const filePath = await videoRenderService.getFilePath(renderId);
|
|
202
|
+
if (!filePath || !existsSync(filePath)) {
|
|
203
|
+
res.status(404).json({ error: 'Rendered file not found' });
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
res.sendFile(filePath);
|
|
207
|
+
} catch (err: any) {
|
|
208
|
+
log('VIDEO', `Failed to serve render file: ${err.message}`);
|
|
209
|
+
res.status(500).json({ error: err.message });
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Serve the built bundle as static files at /api/video/bundle/
|
|
214
|
+
const bundleDir = bundleService.getBundleDir();
|
|
215
|
+
router.use('/bundle', express.static(bundleDir, { maxAge: '1h' }));
|
|
216
|
+
|
|
217
|
+
return router;
|
|
218
|
+
};
|