@assistkick/create 1.10.0 → 1.12.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/src/scaffolder.d.ts +12 -1
- package/dist/src/scaffolder.js +40 -3
- package/dist/src/scaffolder.js.map +1 -1
- package/package.json +1 -1
- package/templates/assistkick-product-system/package.json +1 -1
- package/templates/assistkick-product-system/packages/backend/package.json +1 -0
- package/templates/assistkick-product-system/packages/backend/src/mcp/permission_mcp_server.ts +196 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/agents.ts +31 -7
- package/templates/assistkick-product-system/packages/backend/src/routes/auth.ts +15 -12
- package/templates/assistkick-product-system/packages/backend/src/routes/chat_files.test.ts +95 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/chat_files.ts +97 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/chat_permission.ts +94 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/chat_sessions.ts +189 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/chat_upload.test.ts +131 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/chat_upload.ts +94 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/files.test.ts +12 -3
- package/templates/assistkick-product-system/packages/backend/src/routes/files.ts +2 -2
- package/templates/assistkick-product-system/packages/backend/src/routes/git.ts +390 -22
- package/templates/assistkick-product-system/packages/backend/src/routes/git_branches.test.ts +306 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/git_connect.test.ts +133 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/pipeline.ts +66 -9
- package/templates/assistkick-product-system/packages/backend/src/routes/preview.ts +204 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/projects.test.ts +205 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/projects.ts +37 -9
- package/templates/assistkick-product-system/packages/backend/src/routes/skills.test.ts +139 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/skills.ts +95 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/terminal.ts +5 -4
- package/templates/assistkick-product-system/packages/backend/src/routes/users.ts +4 -4
- package/templates/assistkick-product-system/packages/backend/src/routes/video.ts +8 -8
- package/templates/assistkick-product-system/packages/backend/src/routes/workflow_groups.ts +5 -5
- package/templates/assistkick-product-system/packages/backend/src/routes/workflows.ts +6 -6
- package/templates/assistkick-product-system/packages/backend/src/server.ts +107 -27
- package/templates/assistkick-product-system/packages/backend/src/services/agent_service.test.ts +105 -203
- package/templates/assistkick-product-system/packages/backend/src/services/agent_service.ts +76 -266
- package/templates/assistkick-product-system/packages/backend/src/services/chat_cli_bridge.test.ts +427 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_cli_bridge.ts +345 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_message_repository.test.ts +170 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_message_repository.ts +106 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_session_service.test.ts +217 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_session_service.ts +188 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_ws_handler.test.ts +1243 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_ws_handler.ts +894 -0
- package/templates/assistkick-product-system/packages/backend/src/services/coherence-review.ts +3 -3
- package/templates/assistkick-product-system/packages/backend/src/services/dev_command_detector.test.ts +85 -0
- package/templates/assistkick-product-system/packages/backend/src/services/dev_command_detector.ts +54 -0
- package/templates/assistkick-product-system/packages/backend/src/services/email_service.ts +13 -10
- package/templates/assistkick-product-system/packages/backend/src/services/init.ts +11 -3
- package/templates/assistkick-product-system/packages/backend/src/services/invitation_service.ts +1 -1
- package/templates/assistkick-product-system/packages/backend/src/services/password_reset_service.ts +1 -1
- package/templates/assistkick-product-system/packages/backend/src/services/permission_service.test.ts +243 -0
- package/templates/assistkick-product-system/packages/backend/src/services/permission_service.ts +259 -0
- package/templates/assistkick-product-system/packages/backend/src/services/preview_server_manager.test.ts +172 -0
- package/templates/assistkick-product-system/packages/backend/src/services/preview_server_manager.ts +225 -0
- package/templates/assistkick-product-system/packages/backend/src/services/project_service.test.ts +29 -0
- package/templates/assistkick-product-system/packages/backend/src/services/project_service.ts +17 -0
- package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.test.ts +255 -0
- package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.ts +300 -25
- package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.test.ts +44 -0
- package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.ts +62 -7
- package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.test.ts +77 -6
- package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.ts +129 -8
- package/templates/assistkick-product-system/packages/backend/src/services/terminal_ws_handler.ts +2 -1
- package/templates/assistkick-product-system/packages/backend/src/services/title_generator_service.test.ts +45 -0
- package/templates/assistkick-product-system/packages/backend/src/services/title_generator_service.ts +157 -0
- package/templates/assistkick-product-system/packages/backend/src/services/tts_service.ts +4 -3
- package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.ts +3 -3
- package/templates/assistkick-product-system/packages/frontend/package.json +5 -0
- package/templates/assistkick-product-system/packages/frontend/src/App.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/api/client.ts +336 -5
- package/templates/assistkick-product-system/packages/frontend/src/components/AgentsView.tsx +192 -12
- package/templates/assistkick-product-system/packages/frontend/src/components/AttachmentPreviewList.tsx +98 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/AutocompleteDropdown.tsx +65 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatAttachButton.tsx +56 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatDropZone.tsx +80 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatMessageBubble.tsx +155 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatMessageContent.tsx +182 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatMessageInput.tsx +233 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatSessionSidebar.tsx +218 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatStopButton.tsx +32 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatTodoSidebar.tsx +113 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatView.tsx +842 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/CommitMessageModal.tsx +82 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/DiagramOverlay.tsx +160 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/EditorTabBar.tsx +5 -5
- package/templates/assistkick-product-system/packages/frontend/src/components/FileTree.tsx +9 -10
- package/templates/assistkick-product-system/packages/frontend/src/components/FileTreeInlineInput.tsx +5 -5
- package/templates/assistkick-product-system/packages/frontend/src/components/FilesView.tsx +112 -41
- package/templates/assistkick-product-system/packages/frontend/src/components/GraphLegend.tsx +2 -2
- package/templates/assistkick-product-system/packages/frontend/src/components/HighlightedText.tsx +87 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ImageLightbox.tsx +192 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/KanbanView.tsx +2 -2
- package/templates/assistkick-product-system/packages/frontend/src/components/MentionPill.tsx +33 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/MermaidBlock.tsx +148 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/PermissionDialog.tsx +91 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/PermissionModeSelector.tsx +229 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ProjectSelector.tsx +249 -83
- package/templates/assistkick-product-system/packages/frontend/src/components/QueuedMessageBubble.tsx +38 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/SidePanel.tsx +212 -117
- package/templates/assistkick-product-system/packages/frontend/src/components/SystemPromptAccordion.tsx +48 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/TaskIcon.tsx +11 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/TerminalView.tsx +25 -9
- package/templates/assistkick-product-system/packages/frontend/src/components/ToolDiffView.tsx +114 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ToolResultCard.tsx +87 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ToolUseCard.tsx +149 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/Toolbar.tsx +25 -8
- package/templates/assistkick-product-system/packages/frontend/src/components/UnifiedGitWidget.tsx +722 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/GroupNode.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/NodePalette.tsx +2 -1
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/ProgrammableNode.tsx +178 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowCanvas.tsx +3 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowMonitorModal.tsx +103 -9
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/monitor_nodes.tsx +26 -2
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/workflow_types.ts +42 -1
- package/templates/assistkick-product-system/packages/frontend/src/hooks/useDocumentTitle.ts +11 -0
- package/templates/assistkick-product-system/packages/frontend/src/hooks/useProjects.ts +1 -0
- package/templates/assistkick-product-system/packages/frontend/src/hooks/use_chat_stream.ts +826 -0
- package/templates/assistkick-product-system/packages/frontend/src/hooks/use_file_tree_cache.ts +69 -0
- package/templates/assistkick-product-system/packages/frontend/src/hooks/use_mention_autocomplete.ts +284 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/attachment_manager.test.ts +183 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/attachment_manager.ts +150 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/chat_message_helpers.test.ts +305 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/chat_message_helpers.ts +113 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/context_usage_helpers.test.ts +157 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/context_usage_helpers.ts +95 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/mermaid_helpers.test.ts +65 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/mermaid_helpers.ts +110 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/message_queue.ts +66 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/tool_use_summary.test.ts +124 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/tool_use_summary.ts +112 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/AgentsRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/ChatRoute.tsx +8 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/CoherenceRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/DashboardLayout.tsx +0 -4
- package/templates/assistkick-product-system/packages/frontend/src/routes/DesignSystemRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/FilesRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/GraphRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/KanbanRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/TerminalRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/UsersRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/VideographyRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/WorkflowsRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/accept_invitation.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/forgot_password.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/login.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/register.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/reset_password.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useAttachmentStore.ts +66 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useChatSessionStore.ts +107 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useMessageQueueStore.ts +110 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/usePreviewStore.ts +78 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useProjectStore.ts +7 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useSidePanelStore.ts +6 -1
- package/templates/assistkick-product-system/packages/frontend/src/styles/index.css +30 -357
- package/templates/assistkick-product-system/packages/frontend/src/utils/parse_node_markdown.test.ts +115 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/parse_node_markdown.ts +91 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/preview_utils.test.ts +30 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/preview_utils.ts +3 -0
- package/templates/assistkick-product-system/packages/shared/db/migrate.ts +82 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0000_outgoing_ultron.sql +277 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0015_magenta_jazinda.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0016_giant_xorn.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0017_sloppy_mentor.sql +6 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0018_vengeful_kabuki.sql +9 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0019_careful_sentinels.sql +8 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0020_clever_spot.sql +27 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0021_graceful_hex.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0022_short_kingpin.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0023_ambiguous_sharon_carter.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0024_fat_unus.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0000_snapshot.json +972 -22
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0015_snapshot.json +1552 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0016_snapshot.json +1560 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0017_snapshot.json +1598 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0018_snapshot.json +1657 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0019_snapshot.json +1709 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0020_snapshot.json +1733 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0021_snapshot.json +1740 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0022_snapshot.json +1755 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0023_snapshot.json +1762 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0024_snapshot.json +1769 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/_journal.json +2 -100
- package/templates/assistkick-product-system/packages/shared/db/schema.ts +40 -1
- package/templates/assistkick-product-system/packages/shared/lib/claude-service.test.ts +236 -0
- package/templates/assistkick-product-system/packages/shared/lib/claude-service.ts +46 -5
- package/templates/assistkick-product-system/packages/shared/lib/git_workflow.ts +65 -39
- package/templates/assistkick-product-system/packages/shared/lib/programmable_node_executor.test.ts +173 -0
- package/templates/assistkick-product-system/packages/shared/lib/programmable_node_executor.ts +213 -0
- package/templates/assistkick-product-system/packages/shared/lib/validator.test.ts +70 -0
- package/templates/assistkick-product-system/packages/shared/lib/validator.ts +17 -1
- package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.test.ts +803 -27
- package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.ts +502 -68
- package/templates/assistkick-product-system/packages/shared/lib/workflow_orchestrator.ts +4 -4
- package/templates/assistkick-product-system/packages/shared/package.json +2 -1
- package/templates/assistkick-product-system/packages/shared/test_fixtures/hanging_stream.mjs +46 -0
- package/templates/assistkick-product-system/packages/shared/tools/add_node.test.ts +44 -0
- package/templates/assistkick-product-system/packages/shared/tools/add_node.ts +7 -0
- package/templates/assistkick-product-system/packages/shared/tools/remove_node.ts +2 -1
- package/templates/assistkick-product-system/packages/shared/tools/resolve_question.ts +2 -1
- package/templates/assistkick-product-system/packages/shared/tools/update_node.ts +2 -1
- package/templates/assistkick-product-system/tests/message_queue.test.ts +178 -0
- package/templates/assistkick-product-system/tests/message_queue_per_session.test.ts +143 -0
- package/templates/skills/assistkick-bootstrap/SKILL.md +26 -26
- package/templates/skills/assistkick-code-reviewer/SKILL.md +45 -46
- package/templates/skills/assistkick-db-explorer/SKILL.md +13 -13
- package/templates/skills/assistkick-debugger/SKILL.md +23 -23
- package/templates/skills/assistkick-developer/SKILL.md +59 -63
- package/templates/skills/assistkick-interview/SKILL.md +26 -26
- package/templates/skills/assistkick-video-composition-agent/SKILL.md +231 -0
- package/templates/skills/assistkick-video-script-writer/SKILL.md +136 -0
package/templates/assistkick-product-system/packages/frontend/src/components/workflow/GroupNode.tsx
CHANGED
|
@@ -22,6 +22,7 @@ import { RunAgentNode } from './RunAgentNode';
|
|
|
22
22
|
import { CheckCardPositionNode } from './CheckCardPositionNode';
|
|
23
23
|
import { CheckCycleCountNode } from './CheckCycleCountNode';
|
|
24
24
|
import { SetCardMetadataNode } from './SetCardMetadataNode';
|
|
25
|
+
import { ProgrammableNode } from './ProgrammableNode';
|
|
25
26
|
|
|
26
27
|
/** Node types available inside the group sub-canvas (no nested groups). */
|
|
27
28
|
const INNER_NODE_TYPE_MAP: NodeTypes = {
|
|
@@ -32,6 +33,7 @@ const INNER_NODE_TYPE_MAP: NodeTypes = {
|
|
|
32
33
|
checkCardPosition: CheckCardPositionNode,
|
|
33
34
|
checkCycleCount: CheckCycleCountNode,
|
|
34
35
|
setCardMetadata: SetCardMetadataNode,
|
|
36
|
+
programmable: ProgrammableNode,
|
|
35
37
|
};
|
|
36
38
|
|
|
37
39
|
function GroupSubCanvas({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useState } from 'react';
|
|
2
2
|
import {
|
|
3
|
-
Play, Square, ArrowRightLeft, Bot, GitBranch, RefreshCw, Tag, Layers, Trash2,
|
|
3
|
+
Play, Square, ArrowRightLeft, Bot, GitBranch, RefreshCw, Tag, Layers, Trash2, Code,
|
|
4
4
|
} from 'lucide-react';
|
|
5
5
|
import { NODE_PALETTE_ITEMS } from './workflow_types';
|
|
6
6
|
import type { WorkflowNodeType } from './workflow_types';
|
|
@@ -14,6 +14,7 @@ const NODE_ICONS: Record<string, React.ReactNode> = {
|
|
|
14
14
|
checkCardPosition: <GitBranch size={14} className="text-cyan-400" />,
|
|
15
15
|
checkCycleCount: <RefreshCw size={14} className="text-orange-400" />,
|
|
16
16
|
setCardMetadata: <Tag size={14} className="text-yellow-400" />,
|
|
17
|
+
programmable: <Code size={14} className="text-emerald-400" />,
|
|
17
18
|
};
|
|
18
19
|
|
|
19
20
|
interface WorkflowGroup {
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import React, { useCallback, useState } from 'react';
|
|
2
|
+
import { Handle, Position, NodeResizer, useReactFlow } from '@xyflow/react';
|
|
3
|
+
import type { NodeProps } from '@xyflow/react';
|
|
4
|
+
import { Code, ChevronDown, ChevronRight } from 'lucide-react';
|
|
5
|
+
import Editor from '@monaco-editor/react';
|
|
6
|
+
import { useTheme } from '../../hooks/useTheme';
|
|
7
|
+
|
|
8
|
+
const MIN_WIDTH = 340;
|
|
9
|
+
const MIN_HEIGHT = 320;
|
|
10
|
+
|
|
11
|
+
export function ProgrammableNode({ id, data, selected }: NodeProps) {
|
|
12
|
+
const { updateNodeData } = useReactFlow();
|
|
13
|
+
const { theme } = useTheme();
|
|
14
|
+
const [limitsExpanded, setLimitsExpanded] = useState(false);
|
|
15
|
+
|
|
16
|
+
const label = (data.label as string) || '';
|
|
17
|
+
const code = (data.code as string) || '';
|
|
18
|
+
const timeout = (data.timeout as number) || 30000;
|
|
19
|
+
const memory = (data.memory as number) || 128;
|
|
20
|
+
const maxRequests = (data.maxRequests as number) || 20;
|
|
21
|
+
const maxResponseSize = (data.maxResponseSize as number) || 5242880;
|
|
22
|
+
|
|
23
|
+
const handleLabelChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
|
24
|
+
updateNodeData(id, { label: e.target.value });
|
|
25
|
+
}, [id, updateNodeData]);
|
|
26
|
+
|
|
27
|
+
const handleCodeChange = useCallback((value: string | undefined) => {
|
|
28
|
+
if (value !== undefined) {
|
|
29
|
+
updateNodeData(id, { code: value });
|
|
30
|
+
}
|
|
31
|
+
}, [id, updateNodeData]);
|
|
32
|
+
|
|
33
|
+
const handleLimitChange = useCallback((field: string, value: number) => {
|
|
34
|
+
updateNodeData(id, { [field]: value });
|
|
35
|
+
}, [id, updateNodeData]);
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div className={`rounded-lg border bg-surface-raised text-content flex flex-col h-full ${
|
|
39
|
+
selected ? 'border-accent shadow-[0_0_0_1px_var(--accent)]' : 'border-edge'
|
|
40
|
+
}`}>
|
|
41
|
+
<NodeResizer
|
|
42
|
+
isVisible={selected}
|
|
43
|
+
minWidth={MIN_WIDTH}
|
|
44
|
+
minHeight={MIN_HEIGHT}
|
|
45
|
+
lineClassName="!border-accent"
|
|
46
|
+
handleClassName="!w-2 !h-2 !bg-accent !border-2 !border-surface !rounded-sm"
|
|
47
|
+
/>
|
|
48
|
+
<Handle type="target" position={Position.Top} className="!w-3 !h-3 !bg-accent !border-2 !border-surface" />
|
|
49
|
+
|
|
50
|
+
{/* Header */}
|
|
51
|
+
<div className="flex items-center gap-2 px-4 pt-3 pb-2">
|
|
52
|
+
<Code size={14} className="text-emerald-400" />
|
|
53
|
+
<span className="text-[12px] font-semibold">Programmable</span>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
{/* Label / Node Name */}
|
|
57
|
+
<div className="px-4 pb-2">
|
|
58
|
+
<label className="text-[10px] text-content-muted uppercase tracking-wider">Node Name</label>
|
|
59
|
+
<input
|
|
60
|
+
type="text"
|
|
61
|
+
value={label}
|
|
62
|
+
onChange={handleLabelChange}
|
|
63
|
+
placeholder="e.g. Validate Webhook"
|
|
64
|
+
className="w-full px-2 py-1 rounded border border-edge bg-surface text-[11px] text-content"
|
|
65
|
+
/>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
{/* Monaco Editor — fills remaining vertical space */}
|
|
69
|
+
<div className="px-4 pb-2 flex-1 min-h-0 flex flex-col">
|
|
70
|
+
<label className="text-[10px] text-content-muted uppercase tracking-wider">Code</label>
|
|
71
|
+
<div className="border border-edge rounded overflow-hidden flex-1 min-h-[120px]">
|
|
72
|
+
<Editor
|
|
73
|
+
height="100%"
|
|
74
|
+
language="javascript"
|
|
75
|
+
theme={theme === 'dark' ? 'vs-dark' : 'vs'}
|
|
76
|
+
value={code}
|
|
77
|
+
onChange={handleCodeChange}
|
|
78
|
+
options={{
|
|
79
|
+
minimap: { enabled: false },
|
|
80
|
+
fontSize: 11,
|
|
81
|
+
lineNumbers: 'on',
|
|
82
|
+
scrollBeyondLastLine: false,
|
|
83
|
+
wordWrap: 'on',
|
|
84
|
+
tabSize: 2,
|
|
85
|
+
automaticLayout: true,
|
|
86
|
+
overviewRulerLanes: 0,
|
|
87
|
+
hideCursorInOverviewRuler: true,
|
|
88
|
+
scrollbar: { vertical: 'auto', horizontal: 'auto' },
|
|
89
|
+
}}
|
|
90
|
+
/>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
{/* Execution Limits (collapsible) */}
|
|
95
|
+
<div className="px-4 pb-3 shrink-0">
|
|
96
|
+
<button
|
|
97
|
+
onClick={() => setLimitsExpanded(!limitsExpanded)}
|
|
98
|
+
className="flex items-center gap-1 text-[10px] text-content-muted uppercase tracking-wider hover:text-content transition-colors"
|
|
99
|
+
>
|
|
100
|
+
{limitsExpanded ? <ChevronDown size={10} /> : <ChevronRight size={10} />}
|
|
101
|
+
Execution Limits
|
|
102
|
+
</button>
|
|
103
|
+
|
|
104
|
+
{limitsExpanded && (
|
|
105
|
+
<div className="grid grid-cols-2 gap-2 mt-2">
|
|
106
|
+
<div>
|
|
107
|
+
<label className="text-[9px] text-content-muted">Timeout (ms)</label>
|
|
108
|
+
<input
|
|
109
|
+
type="number"
|
|
110
|
+
min={1000}
|
|
111
|
+
step={1000}
|
|
112
|
+
value={timeout}
|
|
113
|
+
onChange={(e) => handleLimitChange('timeout', parseInt(e.target.value, 10) || 30000)}
|
|
114
|
+
className="w-full px-2 py-1 rounded border border-edge bg-surface text-[10px] text-content"
|
|
115
|
+
/>
|
|
116
|
+
</div>
|
|
117
|
+
<div>
|
|
118
|
+
<label className="text-[9px] text-content-muted">Memory (MB)</label>
|
|
119
|
+
<input
|
|
120
|
+
type="number"
|
|
121
|
+
min={8}
|
|
122
|
+
step={8}
|
|
123
|
+
value={memory}
|
|
124
|
+
onChange={(e) => handleLimitChange('memory', parseInt(e.target.value, 10) || 128)}
|
|
125
|
+
className="w-full px-2 py-1 rounded border border-edge bg-surface text-[10px] text-content"
|
|
126
|
+
/>
|
|
127
|
+
</div>
|
|
128
|
+
<div>
|
|
129
|
+
<label className="text-[9px] text-content-muted">Max Requests</label>
|
|
130
|
+
<input
|
|
131
|
+
type="number"
|
|
132
|
+
min={0}
|
|
133
|
+
value={maxRequests}
|
|
134
|
+
onChange={(e) => handleLimitChange('maxRequests', parseInt(e.target.value, 10) || 20)}
|
|
135
|
+
className="w-full px-2 py-1 rounded border border-edge bg-surface text-[10px] text-content"
|
|
136
|
+
/>
|
|
137
|
+
</div>
|
|
138
|
+
<div>
|
|
139
|
+
<label className="text-[9px] text-content-muted">Max Response (bytes)</label>
|
|
140
|
+
<input
|
|
141
|
+
type="number"
|
|
142
|
+
min={1024}
|
|
143
|
+
step={1024}
|
|
144
|
+
value={maxResponseSize}
|
|
145
|
+
onChange={(e) => handleLimitChange('maxResponseSize', parseInt(e.target.value, 10) || 5242880)}
|
|
146
|
+
className="w-full px-2 py-1 rounded border border-edge bg-surface text-[10px] text-content"
|
|
147
|
+
/>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
)}
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
{/* Output handles: success and error */}
|
|
154
|
+
<div className="flex justify-between px-4 pb-3 text-[9px] text-content-muted shrink-0">
|
|
155
|
+
<div className="relative">
|
|
156
|
+
<span className="px-1.5 py-0.5 rounded bg-surface border border-edge">success</span>
|
|
157
|
+
<Handle
|
|
158
|
+
type="source"
|
|
159
|
+
position={Position.Bottom}
|
|
160
|
+
id="success"
|
|
161
|
+
className="!w-2 !h-2 !bg-green-400 !border !border-surface"
|
|
162
|
+
style={{ left: '30%' }}
|
|
163
|
+
/>
|
|
164
|
+
</div>
|
|
165
|
+
<div className="relative">
|
|
166
|
+
<span className="px-1.5 py-0.5 rounded bg-surface border border-edge">error</span>
|
|
167
|
+
<Handle
|
|
168
|
+
type="source"
|
|
169
|
+
position={Position.Bottom}
|
|
170
|
+
id="error"
|
|
171
|
+
className="!w-2 !h-2 !bg-red-400 !border !border-surface"
|
|
172
|
+
style={{ left: '70%' }}
|
|
173
|
+
/>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
@@ -30,6 +30,7 @@ import { GroupNode } from './GroupNode';
|
|
|
30
30
|
import { RebuildBundleNode } from './RebuildBundleNode';
|
|
31
31
|
import { GenerateTTSNode } from './GenerateTTSNode';
|
|
32
32
|
import { RenderVideoNode } from './RenderVideoNode';
|
|
33
|
+
import { ProgrammableNode } from './ProgrammableNode';
|
|
33
34
|
import { NODE_DEFAULTS } from './workflow_types';
|
|
34
35
|
import type { WorkflowNodeType } from './workflow_types';
|
|
35
36
|
import { autoLayoutNodes } from './autoLayout';
|
|
@@ -47,6 +48,7 @@ const NODE_TYPE_MAP: NodeTypes = {
|
|
|
47
48
|
rebuildBundle: RebuildBundleNode,
|
|
48
49
|
generateTTS: GenerateTTSNode,
|
|
49
50
|
renderVideo: RenderVideoNode,
|
|
51
|
+
programmable: ProgrammableNode,
|
|
50
52
|
};
|
|
51
53
|
|
|
52
54
|
interface ContextMenuState {
|
|
@@ -168,6 +170,7 @@ export function WorkflowCanvasInner({
|
|
|
168
170
|
type,
|
|
169
171
|
position,
|
|
170
172
|
data: defaults(),
|
|
173
|
+
...(type === 'programmable' ? { style: { width: 380, height: 400 } } : {}),
|
|
171
174
|
};
|
|
172
175
|
|
|
173
176
|
setNodes((nds) => [...nds, newNode]);
|
|
@@ -11,9 +11,10 @@ import {
|
|
|
11
11
|
} from '@xyflow/react';
|
|
12
12
|
import '@xyflow/react/dist/style.css';
|
|
13
13
|
import {
|
|
14
|
-
X, Clock, AlertTriangle, CheckCircle2, CircleDot,
|
|
14
|
+
X, Clock, AlertTriangle, CheckCircle2, CircleDot, Square,
|
|
15
15
|
FileText, Pencil, Terminal, Search, FolderOpen, Bot,
|
|
16
16
|
MessageSquare, ChevronDown, ChevronRight, SkipForward, RotateCcw,
|
|
17
|
+
Play,
|
|
17
18
|
} from 'lucide-react';
|
|
18
19
|
import { apiClient, type WorkflowMonitorData, type NodeExecutionData, type ToolCallEntry } from '../../api/client';
|
|
19
20
|
import {
|
|
@@ -28,6 +29,7 @@ import {
|
|
|
28
29
|
MonitorRebuildBundleNode,
|
|
29
30
|
MonitorGenerateTTSNode,
|
|
30
31
|
MonitorRenderVideoNode,
|
|
32
|
+
MonitorProgrammableNode,
|
|
31
33
|
} from './monitor_nodes';
|
|
32
34
|
import { colorizeEdges } from './edgeColors';
|
|
33
35
|
|
|
@@ -119,6 +121,7 @@ const MONITOR_NODE_TYPES: NodeTypes = {
|
|
|
119
121
|
rebuildBundle: MonitorRebuildBundleNode,
|
|
120
122
|
generateTTS: MonitorGenerateTTSNode,
|
|
121
123
|
renderVideo: MonitorRenderVideoNode,
|
|
124
|
+
programmable: MonitorProgrammableNode,
|
|
122
125
|
};
|
|
123
126
|
|
|
124
127
|
function getNodeTypeLabel(type: string): string {
|
|
@@ -134,6 +137,7 @@ function getNodeTypeLabel(type: string): string {
|
|
|
134
137
|
case 'rebuildBundle': return 'Rebuild Bundle';
|
|
135
138
|
case 'generateTTS': return 'Generate TTS';
|
|
136
139
|
case 'renderVideo': return 'Render Video';
|
|
140
|
+
case 'programmable': return 'Programmable';
|
|
137
141
|
default: return type;
|
|
138
142
|
}
|
|
139
143
|
}
|
|
@@ -146,6 +150,7 @@ function getNodeLabel(type: string, data: Record<string, unknown>): string {
|
|
|
146
150
|
case 'setCardMetadata': return `${data.key || '?'} = ${data.value || '?'}`;
|
|
147
151
|
case 'end': return (data.outcome as string) || (data.statusType as string) || '';
|
|
148
152
|
case 'group': return (data.groupName as string) || '';
|
|
153
|
+
case 'programmable': return (data.label as string) || '';
|
|
149
154
|
default: return '';
|
|
150
155
|
}
|
|
151
156
|
}
|
|
@@ -154,6 +159,7 @@ function getNodeIcon(type: string, status: string): React.ReactNode {
|
|
|
154
159
|
if (status === 'running') return <CircleDot size={14} className="text-accent animate-pulse" />;
|
|
155
160
|
if (status === 'completed') return <CheckCircle2 size={14} className="text-emerald-400" />;
|
|
156
161
|
if (status === 'failed') return <AlertTriangle size={14} className="text-error" />;
|
|
162
|
+
if (status === 'stopped') return <Square size={14} className="text-error" />;
|
|
157
163
|
switch (type) {
|
|
158
164
|
case 'runAgent': return <Bot size={14} className="text-purple-400" />;
|
|
159
165
|
default: return <Clock size={14} className="text-content-muted" />;
|
|
@@ -170,6 +176,7 @@ function formatContextWindow(value: number): string {
|
|
|
170
176
|
function StatusBadge({ status }: { status: string }) {
|
|
171
177
|
const cls = status === 'completed' ? 'bg-emerald-500/15 text-emerald-400'
|
|
172
178
|
: status === 'failed' ? 'bg-error/15 text-error'
|
|
179
|
+
: status === 'stopped' ? 'bg-error/15 text-error'
|
|
173
180
|
: status === 'running' ? 'bg-accent/15 text-accent animate-pulse'
|
|
174
181
|
: 'bg-white/[0.06] text-content-muted';
|
|
175
182
|
return (
|
|
@@ -195,7 +202,7 @@ function NodeDetailPanel({
|
|
|
195
202
|
nodeId,
|
|
196
203
|
nodeType,
|
|
197
204
|
nodeData,
|
|
198
|
-
|
|
205
|
+
executions,
|
|
199
206
|
isCurrentNode,
|
|
200
207
|
featureId,
|
|
201
208
|
onClose,
|
|
@@ -204,18 +211,50 @@ function NodeDetailPanel({
|
|
|
204
211
|
nodeId: string;
|
|
205
212
|
nodeType: string;
|
|
206
213
|
nodeData: Record<string, unknown>;
|
|
207
|
-
|
|
214
|
+
executions: NodeExecutionData[];
|
|
208
215
|
isCurrentNode: boolean;
|
|
209
216
|
featureId: string;
|
|
210
217
|
onClose: () => void;
|
|
211
218
|
onAction: () => void;
|
|
212
219
|
}) {
|
|
213
220
|
const toolCallsRef = useRef<HTMLDivElement>(null);
|
|
221
|
+
const hasInitialScrolled = useRef(false);
|
|
214
222
|
const [actionLoading, setActionLoading] = useState<string | null>(null);
|
|
223
|
+
const hasCycles = executions.length > 1;
|
|
224
|
+
const [selectedCycle, setSelectedCycle] = useState<number>(executions.length > 0 ? executions[executions.length - 1].cycle : 1);
|
|
225
|
+
|
|
226
|
+
// Update selected cycle when executions change (new cycle arrives)
|
|
227
|
+
useEffect(() => {
|
|
228
|
+
if (executions.length > 0) {
|
|
229
|
+
const latestCycle = executions[executions.length - 1].cycle;
|
|
230
|
+
setSelectedCycle(latestCycle);
|
|
231
|
+
}
|
|
232
|
+
}, [executions.length]);
|
|
233
|
+
|
|
234
|
+
const execution = executions.find(e => e.cycle === selectedCycle) ?? executions[executions.length - 1] ?? null;
|
|
215
235
|
const statusLabel = execution?.status || 'pending';
|
|
216
236
|
const isAgent = nodeType === 'runAgent';
|
|
217
237
|
const outputData = execution?.outputData;
|
|
218
|
-
const showActions = isCurrentNode && (statusLabel === 'running' || statusLabel === 'failed');
|
|
238
|
+
const showActions = isCurrentNode && (statusLabel === 'running' || statusLabel === 'failed' || statusLabel === 'stopped');
|
|
239
|
+
const showContinue = isCurrentNode && isAgent && execution?.claudeSessionId &&
|
|
240
|
+
(statusLabel === 'failed' || statusLabel === 'stopped' || (statusLabel === 'running' && !execution?.processAlive));
|
|
241
|
+
|
|
242
|
+
// Auto-scroll activity log to bottom on initial open only
|
|
243
|
+
useEffect(() => {
|
|
244
|
+
if (!hasInitialScrolled.current && toolCallsRef.current && execution?.toolCalls && execution.toolCalls.length > 0) {
|
|
245
|
+
toolCallsRef.current.scrollTop = toolCallsRef.current.scrollHeight;
|
|
246
|
+
hasInitialScrolled.current = true;
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
const handleStop = async () => {
|
|
251
|
+
setActionLoading('stop');
|
|
252
|
+
try {
|
|
253
|
+
await apiClient.stopNode(featureId, nodeId);
|
|
254
|
+
onAction();
|
|
255
|
+
} catch { /* error is non-critical — polling will pick up the state */ }
|
|
256
|
+
setActionLoading(null);
|
|
257
|
+
};
|
|
219
258
|
|
|
220
259
|
const handleForceNext = async () => {
|
|
221
260
|
setActionLoading('force-next');
|
|
@@ -235,6 +274,15 @@ function NodeDetailPanel({
|
|
|
235
274
|
setActionLoading(null);
|
|
236
275
|
};
|
|
237
276
|
|
|
277
|
+
const handleContinue = async () => {
|
|
278
|
+
setActionLoading('continue');
|
|
279
|
+
try {
|
|
280
|
+
await apiClient.continueNode(featureId, nodeId);
|
|
281
|
+
onAction();
|
|
282
|
+
} catch { /* error is non-critical — polling will pick up the state */ }
|
|
283
|
+
setActionLoading(null);
|
|
284
|
+
};
|
|
285
|
+
|
|
238
286
|
return (
|
|
239
287
|
<div className="w-1/2 shrink-0 border-l border-edge bg-surface flex flex-col overflow-hidden">
|
|
240
288
|
{/* Header */}
|
|
@@ -247,6 +295,28 @@ function NodeDetailPanel({
|
|
|
247
295
|
<StatusBadge status={statusLabel} />
|
|
248
296
|
{showActions && (
|
|
249
297
|
<div className="flex items-center gap-1.5 ml-2">
|
|
298
|
+
{statusLabel === 'running' && (
|
|
299
|
+
<button
|
|
300
|
+
onClick={handleStop}
|
|
301
|
+
disabled={actionLoading !== null}
|
|
302
|
+
className="flex items-center gap-1 bg-transparent border border-error/40 text-error rounded px-2 py-1 text-[11px] cursor-pointer font-mono hover:bg-error/10 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
|
303
|
+
title="Stop this node"
|
|
304
|
+
>
|
|
305
|
+
<Square size={11} />
|
|
306
|
+
{actionLoading === 'stop' ? 'Stopping...' : 'Stop'}
|
|
307
|
+
</button>
|
|
308
|
+
)}
|
|
309
|
+
{showContinue && (
|
|
310
|
+
<button
|
|
311
|
+
onClick={handleContinue}
|
|
312
|
+
disabled={actionLoading !== null}
|
|
313
|
+
className="flex items-center gap-1 bg-transparent border border-emerald-400/40 text-emerald-400 rounded px-2 py-1 text-[11px] cursor-pointer font-mono hover:bg-emerald-400/10 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
|
314
|
+
title="Resume Claude session"
|
|
315
|
+
>
|
|
316
|
+
<Play size={11} />
|
|
317
|
+
{actionLoading === 'continue' ? 'Continuing...' : 'Continue'}
|
|
318
|
+
</button>
|
|
319
|
+
)}
|
|
250
320
|
<button
|
|
251
321
|
onClick={handleRestart}
|
|
252
322
|
disabled={actionLoading !== null}
|
|
@@ -276,6 +346,26 @@ function NodeDetailPanel({
|
|
|
276
346
|
</button>
|
|
277
347
|
</div>
|
|
278
348
|
|
|
349
|
+
{/* Cycle tabs — only shown when multiple cycles exist */}
|
|
350
|
+
{hasCycles && (
|
|
351
|
+
<div className="flex items-center gap-1 px-6 py-2 border-b border-edge bg-surface-alt">
|
|
352
|
+
<span className="text-[10px] font-bold uppercase tracking-widest text-content-muted mr-2">Cycle</span>
|
|
353
|
+
{executions.map(ex => (
|
|
354
|
+
<button
|
|
355
|
+
key={ex.cycle}
|
|
356
|
+
onClick={() => setSelectedCycle(ex.cycle)}
|
|
357
|
+
className={`px-2.5 py-1 rounded text-[11px] font-mono font-semibold cursor-pointer transition-colors ${
|
|
358
|
+
selectedCycle === ex.cycle
|
|
359
|
+
? 'bg-accent/20 text-accent border border-accent/40'
|
|
360
|
+
: 'bg-white/[0.04] text-content-muted border border-edge hover:bg-white/[0.08] hover:text-content'
|
|
361
|
+
}`}
|
|
362
|
+
>
|
|
363
|
+
{ex.cycle}
|
|
364
|
+
</button>
|
|
365
|
+
))}
|
|
366
|
+
</div>
|
|
367
|
+
)}
|
|
368
|
+
|
|
279
369
|
{/* Content */}
|
|
280
370
|
<div className="flex-1 overflow-y-auto px-6 py-5 space-y-5">
|
|
281
371
|
{/* Timing */}
|
|
@@ -437,15 +527,18 @@ function MonitorGraphView({
|
|
|
437
527
|
// Convert graph data to React Flow nodes/edges with execution state
|
|
438
528
|
const nodes: Node[] = useMemo(() =>
|
|
439
529
|
monitorData.graphData.nodes.map(n => {
|
|
440
|
-
const
|
|
530
|
+
const executions = monitorData.nodeExecutions[n.id];
|
|
531
|
+
const latestExec = executions?.[executions.length - 1] ?? null;
|
|
441
532
|
const isCurrent = n.id === monitorData.currentNodeId && monitorData.status === 'running';
|
|
442
|
-
const status =
|
|
533
|
+
const status = latestExec?.status || (isCurrent ? 'running' : 'pending');
|
|
534
|
+
const execCount = executions?.length ?? 0;
|
|
443
535
|
return {
|
|
444
536
|
...n,
|
|
445
537
|
data: {
|
|
446
538
|
...n.data,
|
|
447
539
|
_execStatus: status,
|
|
448
|
-
_error:
|
|
540
|
+
_error: latestExec?.error || null,
|
|
541
|
+
_execCount: execCount,
|
|
449
542
|
_onNodeClick: onNodeClick,
|
|
450
543
|
},
|
|
451
544
|
};
|
|
@@ -568,7 +661,7 @@ export function WorkflowMonitorModal({ featureId, featureName, isOpen, onClose }
|
|
|
568
661
|
if (!isOpen) return null;
|
|
569
662
|
|
|
570
663
|
const selectedNode = monitorData?.graphData.nodes.find(n => n.id === selectedNodeId);
|
|
571
|
-
const
|
|
664
|
+
const selectedExecs = selectedNodeId ? monitorData?.nodeExecutions[selectedNodeId] || [] : [];
|
|
572
665
|
|
|
573
666
|
return (
|
|
574
667
|
<div className="fixed inset-0 z-[300]">
|
|
@@ -624,10 +717,11 @@ export function WorkflowMonitorModal({ featureId, featureName, isOpen, onClose }
|
|
|
624
717
|
{/* Side panel */}
|
|
625
718
|
{selectedNode && (
|
|
626
719
|
<NodeDetailPanel
|
|
720
|
+
key={selectedNodeId}
|
|
627
721
|
nodeId={selectedNodeId!}
|
|
628
722
|
nodeType={selectedNode.type}
|
|
629
723
|
nodeData={selectedNode.data}
|
|
630
|
-
|
|
724
|
+
executions={selectedExecs}
|
|
631
725
|
isCurrentNode={selectedNodeId === monitorData.currentNodeId}
|
|
632
726
|
featureId={featureId}
|
|
633
727
|
onClose={() => setSelectedNodeId(null)}
|
|
@@ -9,7 +9,7 @@ import { Handle, Position } from '@xyflow/react';
|
|
|
9
9
|
import type { NodeProps } from '@xyflow/react';
|
|
10
10
|
import {
|
|
11
11
|
Play, Square, ArrowLeftRight, Bot, MapPin, RefreshCw, Tag, Layers,
|
|
12
|
-
CircleDot, CheckCircle2, AlertTriangle, Clock, Package, Volume2, Film,
|
|
12
|
+
CircleDot, CheckCircle2, AlertTriangle, Clock, Package, Volume2, Film, Code,
|
|
13
13
|
} from 'lucide-react';
|
|
14
14
|
|
|
15
15
|
/* ── Execution status styling ── */
|
|
@@ -18,12 +18,14 @@ const STATUS_RING: Record<string, string> = {
|
|
|
18
18
|
running: 'ring-2 ring-accent animate-pulse',
|
|
19
19
|
completed: 'ring-2 ring-emerald-400',
|
|
20
20
|
failed: 'ring-2 ring-error',
|
|
21
|
+
stopped: 'ring-2 ring-error',
|
|
21
22
|
};
|
|
22
23
|
|
|
23
24
|
const STATUS_BG: Record<string, string> = {
|
|
24
25
|
running: 'bg-accent/10',
|
|
25
26
|
completed: 'bg-emerald-500/10',
|
|
26
27
|
failed: 'bg-error/10',
|
|
28
|
+
stopped: 'bg-error/10',
|
|
27
29
|
pending: 'bg-white/[0.03]',
|
|
28
30
|
};
|
|
29
31
|
|
|
@@ -31,6 +33,7 @@ function statusIcon(status: string, size = 14) {
|
|
|
31
33
|
if (status === 'running') return <CircleDot size={size} className="text-accent animate-pulse" />;
|
|
32
34
|
if (status === 'completed') return <CheckCircle2 size={size} className="text-emerald-400" />;
|
|
33
35
|
if (status === 'failed') return <AlertTriangle size={size} className="text-error" />;
|
|
36
|
+
if (status === 'stopped') return <Square size={size} className="text-error" />;
|
|
34
37
|
return <Clock size={size} className="text-content-muted" />;
|
|
35
38
|
}
|
|
36
39
|
|
|
@@ -57,15 +60,22 @@ function MonitorNodeWrapper({
|
|
|
57
60
|
}) {
|
|
58
61
|
const execStatus = (data._execStatus as string) || 'pending';
|
|
59
62
|
const error = data._error as string | null;
|
|
63
|
+
const execCount = (data._execCount as number) || 0;
|
|
60
64
|
const onNodeClick = data._onNodeClick as ((nodeId: string) => void) | undefined;
|
|
61
65
|
const ringCls = STATUS_RING[execStatus] || '';
|
|
62
66
|
const bgCls = STATUS_BG[execStatus] || STATUS_BG.pending;
|
|
63
67
|
|
|
64
68
|
return (
|
|
65
69
|
<div
|
|
66
|
-
className={`px-4 py-3 rounded-lg border border-edge ${bgCls} ${ringCls} text-content min-w-[160px] cursor-pointer transition-all hover:border-content/30`}
|
|
70
|
+
className={`px-4 py-3 rounded-lg border border-edge ${bgCls} ${ringCls} text-content min-w-[160px] cursor-pointer transition-all hover:border-content/30 relative`}
|
|
67
71
|
onClick={(e) => { e.stopPropagation(); onNodeClick?.(id); }}
|
|
68
72
|
>
|
|
73
|
+
{/* Execution count badge — shown when node has been executed more than once */}
|
|
74
|
+
{execCount > 1 && (
|
|
75
|
+
<div className="absolute -top-2 -right-2 w-5 h-5 rounded-full bg-accent text-surface text-[10px] font-bold flex items-center justify-center shadow-sm">
|
|
76
|
+
{execCount}
|
|
77
|
+
</div>
|
|
78
|
+
)}
|
|
69
79
|
{hasTarget && (
|
|
70
80
|
<Handle type="target" position={Position.Top} className="!w-3 !h-3 !bg-accent !border-2 !border-surface" />
|
|
71
81
|
)}
|
|
@@ -244,3 +254,17 @@ export function MonitorRenderVideoNode({ id, data }: NodeProps) {
|
|
|
244
254
|
/>
|
|
245
255
|
);
|
|
246
256
|
}
|
|
257
|
+
|
|
258
|
+
export function MonitorProgrammableNode({ id, data }: NodeProps) {
|
|
259
|
+
const d = data as Record<string, unknown>;
|
|
260
|
+
return (
|
|
261
|
+
<MonitorNodeWrapper
|
|
262
|
+
id={id}
|
|
263
|
+
data={d}
|
|
264
|
+
icon={<Code size={14} className="text-emerald-400" />}
|
|
265
|
+
label="Programmable"
|
|
266
|
+
detail={(d.label as string) || ''}
|
|
267
|
+
sourceHandles={['success', 'error']}
|
|
268
|
+
/>
|
|
269
|
+
);
|
|
270
|
+
}
|
|
@@ -12,6 +12,7 @@ export const WORKFLOW_NODE_TYPES = [
|
|
|
12
12
|
'rebuildBundle',
|
|
13
13
|
'generateTTS',
|
|
14
14
|
'renderVideo',
|
|
15
|
+
'programmable',
|
|
15
16
|
] as const;
|
|
16
17
|
|
|
17
18
|
export type WorkflowNodeType = typeof WORKFLOW_NODE_TYPES[number];
|
|
@@ -89,6 +90,21 @@ export interface RenderVideoData {
|
|
|
89
90
|
fileOutputPrefix: string;
|
|
90
91
|
}
|
|
91
92
|
|
|
93
|
+
export interface ProgrammableNodeData {
|
|
94
|
+
/** User-visible name — used as key in context.nodeOutputs */
|
|
95
|
+
label: string;
|
|
96
|
+
/** User-written JavaScript code containing async function run(context) { ... } */
|
|
97
|
+
code: string;
|
|
98
|
+
/** Execution timeout in milliseconds (default: 30000) */
|
|
99
|
+
timeout: number;
|
|
100
|
+
/** V8 isolate memory limit in MB (default: 128) */
|
|
101
|
+
memory: number;
|
|
102
|
+
/** Max HTTP requests per execution (default: 20) */
|
|
103
|
+
maxRequests: number;
|
|
104
|
+
/** Max HTTP response body size in bytes (default: 5242880) */
|
|
105
|
+
maxResponseSize: number;
|
|
106
|
+
}
|
|
107
|
+
|
|
92
108
|
export type WorkflowNodeData =
|
|
93
109
|
| ({ type: 'start' } & StartNodeData)
|
|
94
110
|
| ({ type: 'transitionCard' } & TransitionCardData)
|
|
@@ -100,7 +116,8 @@ export type WorkflowNodeData =
|
|
|
100
116
|
| ({ type: 'group' } & GroupNodeData)
|
|
101
117
|
| ({ type: 'rebuildBundle' } & RebuildBundleData)
|
|
102
118
|
| ({ type: 'generateTTS' } & GenerateTTSData)
|
|
103
|
-
| ({ type: 'renderVideo' } & RenderVideoData)
|
|
119
|
+
| ({ type: 'renderVideo' } & RenderVideoData)
|
|
120
|
+
| ({ type: 'programmable' } & ProgrammableNodeData);
|
|
104
121
|
|
|
105
122
|
export interface NodePaletteItem {
|
|
106
123
|
type: WorkflowNodeType;
|
|
@@ -119,8 +136,31 @@ export const NODE_PALETTE_ITEMS: NodePaletteItem[] = [
|
|
|
119
136
|
{ type: 'rebuildBundle', label: 'Rebuild Bundle', description: 'Build Remotion webpack bundle' },
|
|
120
137
|
{ type: 'generateTTS', label: 'Generate TTS', description: 'Generate text-to-speech audio' },
|
|
121
138
|
{ type: 'renderVideo', label: 'Render Video', description: 'Render composition to MP4' },
|
|
139
|
+
{ type: 'programmable', label: 'Programmable', description: 'Run custom JavaScript code' },
|
|
122
140
|
];
|
|
123
141
|
|
|
142
|
+
const DEFAULT_PROGRAMMABLE_CODE = `async function run(context) {
|
|
143
|
+
// Destructure the execution context
|
|
144
|
+
const { featureId, projectId, cycle, nodeOutputs } = context;
|
|
145
|
+
|
|
146
|
+
// Access output from a previous node
|
|
147
|
+
const prevResult = nodeOutputs["Previous Node"];
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
// Example: make an HTTP request using the SSRF-protected fetch bridge
|
|
151
|
+
const url = "https://api.example.com/data";
|
|
152
|
+
const response = await fetch(url);
|
|
153
|
+
const data = await response.json();
|
|
154
|
+
|
|
155
|
+
// Return a value to follow the success output handle
|
|
156
|
+
return { data };
|
|
157
|
+
} catch (error) {
|
|
158
|
+
// Throw to follow the error output handle
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
`;
|
|
163
|
+
|
|
124
164
|
export const NODE_DEFAULTS: Record<string, () => Record<string, unknown>> = {
|
|
125
165
|
start: () => ({}),
|
|
126
166
|
transitionCard: () => ({ sourceColumn: 'todo', targetColumn: 'in_progress' }),
|
|
@@ -133,4 +173,5 @@ export const NODE_DEFAULTS: Record<string, () => Record<string, unknown>> = {
|
|
|
133
173
|
rebuildBundle: () => ({}),
|
|
134
174
|
generateTTS: () => ({ scriptPath: '', force: false, voiceId: '' }),
|
|
135
175
|
renderVideo: () => ({ compositionId: '', resolution: '1920x1080', aspectRatio: '', fileOutputPrefix: '' }),
|
|
176
|
+
programmable: () => ({ label: '', code: DEFAULT_PROGRAMMABLE_CODE, timeout: 30000, memory: 128, maxRequests: 20, maxResponseSize: 5242880 }),
|
|
136
177
|
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Sets document.title to 'AK-{title}' when the component mounts
|
|
5
|
+
* and updates on title changes.
|
|
6
|
+
*/
|
|
7
|
+
export function useDocumentTitle(title: string): void {
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
document.title = `AK-${title}`;
|
|
10
|
+
}, [title]);
|
|
11
|
+
}
|
|
@@ -19,6 +19,7 @@ export function useProjects() {
|
|
|
19
19
|
selectProject: store.selectProject,
|
|
20
20
|
createProject: store.createProject,
|
|
21
21
|
renameProject: store.renameProject,
|
|
22
|
+
updatePreviewCommand: store.updatePreviewCommand,
|
|
22
23
|
archiveProject: store.archiveProject,
|
|
23
24
|
refetchProjects: store.refetchProjects,
|
|
24
25
|
};
|