@assistkick/create 1.6.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/package.json +2 -2
- 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/index.html +3 -0
- package/templates/assistkick-product-system/packages/frontend/package-lock.json +800 -11
- package/templates/assistkick-product-system/packages/frontend/package.json +11 -1
- package/templates/assistkick-product-system/packages/frontend/src/App.tsx +24 -7
- 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 +383 -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 +193 -64
- 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 +226 -291
- 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 +40 -66
- package/templates/assistkick-product-system/packages/frontend/src/components/SidePanel.tsx +55 -115
- package/templates/assistkick-product-system/packages/frontend/src/components/TerminalView.tsx +121 -52
- package/templates/assistkick-product-system/packages/frontend/src/components/Toolbar.tsx +155 -77
- 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 +270 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/KanbanCardShowcase.tsx +37 -0
- 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 +207 -0
- 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/useGraph.ts +6 -21
- package/templates/assistkick-product-system/packages/frontend/src/hooks/useProjects.ts +15 -80
- 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 +19 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/DashboardLayout.tsx +54 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/DesignSystemRoute.tsx +6 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/FilesRoute.tsx +13 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/GraphRoute.tsx +93 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/KanbanRoute.tsx +30 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/TerminalRoute.tsx +9 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/UsersRoute.tsx +6 -0
- 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/useGitModalStore.ts +14 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useGraphStore.ts +36 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useGraphUIStore.ts +25 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useProjectStore.ts +90 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useQaSheetStore.ts +27 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useSidePanelStore.ts +76 -0
- package/templates/assistkick-product-system/packages/frontend/src/styles/index.css +336 -3632
- 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 +7 -1
- 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 +16 -5
- 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-bootstrap/SKILL.md +3 -3
- package/templates/skills/assistkick-code-reviewer/SKILL.md +2 -2
- package/templates/skills/assistkick-debugger/SKILL.md +2 -2
- package/templates/skills/assistkick-developer/SKILL.md +6 -3
- package/templates/skills/assistkick-developer/references/react_development_guidelines.md +225 -0
- package/templates/skills/assistkick-interview/SKILL.md +2 -2
- package/templates/skills/product-system/graph.json +1890 -0
- package/templates/skills/product-system/kanban.json +304 -0
- package/templates/skills/product-system/nodes/comp_001.md +56 -0
- package/templates/skills/product-system/nodes/comp_002.md +57 -0
- package/templates/skills/product-system/nodes/data_001.md +51 -0
- package/templates/skills/product-system/nodes/data_002.md +40 -0
- package/templates/skills/product-system/nodes/data_004.md +38 -0
- package/templates/skills/product-system/nodes/dec_001.md +34 -0
- package/templates/skills/product-system/nodes/dec_016.md +32 -0
- package/templates/skills/product-system/nodes/feat_008.md +30 -0
- package/templates/skills/video-composition-agent/SKILL.md +232 -0
- package/templates/skills/video-script-writer/SKILL.md +136 -0
|
@@ -0,0 +1,634 @@
|
|
|
1
|
+
import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
ReactFlow,
|
|
4
|
+
MiniMap,
|
|
5
|
+
Background,
|
|
6
|
+
BackgroundVariant,
|
|
7
|
+
ReactFlowProvider,
|
|
8
|
+
type Node,
|
|
9
|
+
type Edge,
|
|
10
|
+
type NodeTypes,
|
|
11
|
+
} from '@xyflow/react';
|
|
12
|
+
import '@xyflow/react/dist/style.css';
|
|
13
|
+
import {
|
|
14
|
+
X, Clock, AlertTriangle, CheckCircle2, CircleDot,
|
|
15
|
+
FileText, Pencil, Terminal, Search, FolderOpen, Bot,
|
|
16
|
+
MessageSquare, ChevronDown, ChevronRight, SkipForward, RotateCcw,
|
|
17
|
+
} from 'lucide-react';
|
|
18
|
+
import { apiClient, type WorkflowMonitorData, type NodeExecutionData, type ToolCallEntry } from '../../api/client';
|
|
19
|
+
import {
|
|
20
|
+
MonitorStartNode,
|
|
21
|
+
MonitorEndNode,
|
|
22
|
+
MonitorTransitionCardNode,
|
|
23
|
+
MonitorRunAgentNode,
|
|
24
|
+
MonitorCheckCardPositionNode,
|
|
25
|
+
MonitorCheckCycleCountNode,
|
|
26
|
+
MonitorSetCardMetadataNode,
|
|
27
|
+
MonitorGroupNode,
|
|
28
|
+
} from './monitor_nodes';
|
|
29
|
+
import { colorizeEdges } from './edgeColors';
|
|
30
|
+
|
|
31
|
+
/* ── Types ── */
|
|
32
|
+
|
|
33
|
+
interface WorkflowMonitorModalProps {
|
|
34
|
+
featureId: string;
|
|
35
|
+
featureName: string;
|
|
36
|
+
isOpen: boolean;
|
|
37
|
+
onClose: () => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* ── Tool icons ── */
|
|
41
|
+
|
|
42
|
+
const TOOL_ICONS: Record<string, React.ReactNode> = {
|
|
43
|
+
Read: <FileText size={12} className="text-blue-400" />,
|
|
44
|
+
Write: <Pencil size={12} className="text-emerald-400" />,
|
|
45
|
+
Edit: <Pencil size={12} className="text-amber-400" />,
|
|
46
|
+
Bash: <Terminal size={12} className="text-purple-400" />,
|
|
47
|
+
Grep: <Search size={12} className="text-cyan-400" />,
|
|
48
|
+
Glob: <FolderOpen size={12} className="text-orange-400" />,
|
|
49
|
+
__assistant__: <MessageSquare size={12} className="text-sky-400" />,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const CHAR_LIMIT = 200;
|
|
53
|
+
|
|
54
|
+
/* ── Expandable activity row ── */
|
|
55
|
+
|
|
56
|
+
function ActivityRow({ tc, isFirst }: { tc: ToolCallEntry; isFirst: boolean }) {
|
|
57
|
+
const [expanded, setExpanded] = useState(false);
|
|
58
|
+
const isAssistant = tc.toolName === '__assistant__';
|
|
59
|
+
const content = tc.target || '\u2014';
|
|
60
|
+
const isLong = content.length > CHAR_LIMIT;
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<div className={`px-4 py-2.5 ${!isFirst ? 'border-t border-edge' : ''}`}>
|
|
64
|
+
<div className="flex items-start gap-3">
|
|
65
|
+
<span className="shrink-0 mt-0.5">
|
|
66
|
+
{TOOL_ICONS[tc.toolName] || <Terminal size={12} className="text-content-muted" />}
|
|
67
|
+
</span>
|
|
68
|
+
<div className="min-w-0 flex-1">
|
|
69
|
+
<div className="flex items-center gap-2">
|
|
70
|
+
<span className="text-[11px] font-mono font-semibold text-content">
|
|
71
|
+
{isAssistant ? 'Assistant' : tc.toolName}
|
|
72
|
+
</span>
|
|
73
|
+
<span className="text-[10px] font-mono text-content-muted">{formatTimeShort(tc.timestamp)}</span>
|
|
74
|
+
</div>
|
|
75
|
+
{isLong ? (
|
|
76
|
+
<div>
|
|
77
|
+
<button
|
|
78
|
+
onClick={() => setExpanded(!expanded)}
|
|
79
|
+
className="flex items-center gap-1 text-[10px] text-content-muted hover:text-content cursor-pointer mt-0.5"
|
|
80
|
+
>
|
|
81
|
+
{expanded ? <ChevronDown size={10} /> : <ChevronRight size={10} />}
|
|
82
|
+
<span>{expanded ? 'Collapse' : 'Expand'}</span>
|
|
83
|
+
</button>
|
|
84
|
+
{expanded ? (
|
|
85
|
+
<div className="text-[11px] font-mono text-content-secondary whitespace-pre-wrap break-words mt-1">
|
|
86
|
+
{content}
|
|
87
|
+
</div>
|
|
88
|
+
) : (
|
|
89
|
+
<div className="text-[11px] font-mono text-content-secondary break-words mt-1">
|
|
90
|
+
{content.slice(0, CHAR_LIMIT)}...
|
|
91
|
+
</div>
|
|
92
|
+
)}
|
|
93
|
+
</div>
|
|
94
|
+
) : (
|
|
95
|
+
<div className="text-[11px] font-mono text-content-secondary whitespace-pre-wrap break-words">
|
|
96
|
+
{content}
|
|
97
|
+
</div>
|
|
98
|
+
)}
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* ── Monitor Node Types map ── */
|
|
106
|
+
|
|
107
|
+
const MONITOR_NODE_TYPES: NodeTypes = {
|
|
108
|
+
start: MonitorStartNode,
|
|
109
|
+
end: MonitorEndNode,
|
|
110
|
+
transitionCard: MonitorTransitionCardNode,
|
|
111
|
+
runAgent: MonitorRunAgentNode,
|
|
112
|
+
checkCardPosition: MonitorCheckCardPositionNode,
|
|
113
|
+
checkCycleCount: MonitorCheckCycleCountNode,
|
|
114
|
+
setCardMetadata: MonitorSetCardMetadataNode,
|
|
115
|
+
group: MonitorGroupNode,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
function getNodeTypeLabel(type: string): string {
|
|
119
|
+
switch (type) {
|
|
120
|
+
case 'start': return 'Start';
|
|
121
|
+
case 'end': return 'End';
|
|
122
|
+
case 'transitionCard': return 'Transition Card';
|
|
123
|
+
case 'runAgent': return 'Run Agent';
|
|
124
|
+
case 'checkCardPosition': return 'Check Position';
|
|
125
|
+
case 'checkCycleCount': return 'Check Cycles';
|
|
126
|
+
case 'setCardMetadata': return 'Set Metadata';
|
|
127
|
+
case 'group': return 'Group';
|
|
128
|
+
default: return type;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function getNodeLabel(type: string, data: Record<string, unknown>): string {
|
|
133
|
+
switch (type) {
|
|
134
|
+
case 'runAgent': return (data.agentName as string) || (data.agentId as string) || '';
|
|
135
|
+
case 'transitionCard': return `${data.fromColumn || '?'} \u2192 ${data.toColumn || '?'}`;
|
|
136
|
+
case 'checkCycleCount': return `max: ${data.maxCycles || '?'}`;
|
|
137
|
+
case 'setCardMetadata': return `${data.key || '?'} = ${data.value || '?'}`;
|
|
138
|
+
case 'end': return (data.outcome as string) || (data.statusType as string) || '';
|
|
139
|
+
case 'group': return (data.groupName as string) || '';
|
|
140
|
+
default: return '';
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function getNodeIcon(type: string, status: string): React.ReactNode {
|
|
145
|
+
if (status === 'running') return <CircleDot size={14} className="text-accent animate-pulse" />;
|
|
146
|
+
if (status === 'completed') return <CheckCircle2 size={14} className="text-emerald-400" />;
|
|
147
|
+
if (status === 'failed') return <AlertTriangle size={14} className="text-error" />;
|
|
148
|
+
switch (type) {
|
|
149
|
+
case 'runAgent': return <Bot size={14} className="text-purple-400" />;
|
|
150
|
+
default: return <Clock size={14} className="text-content-muted" />;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function formatContextWindow(value: number): string {
|
|
155
|
+
const pct = Math.round((value / 200_000) * 100);
|
|
156
|
+
return `${pct}%`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/* ── Status badge ── */
|
|
160
|
+
|
|
161
|
+
function StatusBadge({ status }: { status: string }) {
|
|
162
|
+
const cls = status === 'completed' ? 'bg-emerald-500/15 text-emerald-400'
|
|
163
|
+
: status === 'failed' ? 'bg-error/15 text-error'
|
|
164
|
+
: status === 'running' ? 'bg-accent/15 text-accent animate-pulse'
|
|
165
|
+
: 'bg-white/[0.06] text-content-muted';
|
|
166
|
+
return (
|
|
167
|
+
<span className={`rounded-full px-2.5 py-0.5 text-[10px] font-bold uppercase tracking-wider ${cls}`}>
|
|
168
|
+
{status}
|
|
169
|
+
</span>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/* ── Section label (matches DS pattern) ── */
|
|
174
|
+
|
|
175
|
+
function SectionLabel({ children }: { children: React.ReactNode }) {
|
|
176
|
+
return (
|
|
177
|
+
<h4 className="mb-3 text-[11px] font-bold uppercase tracking-widest text-content-muted">
|
|
178
|
+
{children}
|
|
179
|
+
</h4>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/* ── Side Panel ── */
|
|
184
|
+
|
|
185
|
+
function NodeDetailPanel({
|
|
186
|
+
nodeId,
|
|
187
|
+
nodeType,
|
|
188
|
+
nodeData,
|
|
189
|
+
execution,
|
|
190
|
+
isCurrentNode,
|
|
191
|
+
featureId,
|
|
192
|
+
onClose,
|
|
193
|
+
onAction,
|
|
194
|
+
}: {
|
|
195
|
+
nodeId: string;
|
|
196
|
+
nodeType: string;
|
|
197
|
+
nodeData: Record<string, unknown>;
|
|
198
|
+
execution: NodeExecutionData | null;
|
|
199
|
+
isCurrentNode: boolean;
|
|
200
|
+
featureId: string;
|
|
201
|
+
onClose: () => void;
|
|
202
|
+
onAction: () => void;
|
|
203
|
+
}) {
|
|
204
|
+
const toolCallsRef = useRef<HTMLDivElement>(null);
|
|
205
|
+
const [actionLoading, setActionLoading] = useState<string | null>(null);
|
|
206
|
+
const statusLabel = execution?.status || 'pending';
|
|
207
|
+
const isAgent = nodeType === 'runAgent';
|
|
208
|
+
const outputData = execution?.outputData;
|
|
209
|
+
const showActions = isCurrentNode && (statusLabel === 'running' || statusLabel === 'failed');
|
|
210
|
+
|
|
211
|
+
const handleForceNext = async () => {
|
|
212
|
+
setActionLoading('force-next');
|
|
213
|
+
try {
|
|
214
|
+
await apiClient.forceNextNode(featureId, nodeId);
|
|
215
|
+
onAction();
|
|
216
|
+
} catch { /* error is non-critical — polling will pick up the state */ }
|
|
217
|
+
setActionLoading(null);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const handleRestart = async () => {
|
|
221
|
+
setActionLoading('restart');
|
|
222
|
+
try {
|
|
223
|
+
await apiClient.restartNode(featureId, nodeId);
|
|
224
|
+
onAction();
|
|
225
|
+
} catch { /* error is non-critical — polling will pick up the state */ }
|
|
226
|
+
setActionLoading(null);
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
return (
|
|
230
|
+
<div className="w-1/2 shrink-0 border-l border-edge bg-surface flex flex-col overflow-hidden">
|
|
231
|
+
{/* Header */}
|
|
232
|
+
<div className="flex items-center justify-between px-6 py-4 border-b border-edge bg-surface-alt">
|
|
233
|
+
<div className="flex items-center gap-3 min-w-0">
|
|
234
|
+
{getNodeIcon(nodeType, statusLabel)}
|
|
235
|
+
<span className="text-[13px] font-bold uppercase tracking-widest text-content">
|
|
236
|
+
{getNodeTypeLabel(nodeType)}
|
|
237
|
+
</span>
|
|
238
|
+
<StatusBadge status={statusLabel} />
|
|
239
|
+
{showActions && (
|
|
240
|
+
<div className="flex items-center gap-1.5 ml-2">
|
|
241
|
+
<button
|
|
242
|
+
onClick={handleRestart}
|
|
243
|
+
disabled={actionLoading !== null}
|
|
244
|
+
className="flex items-center gap-1 bg-transparent border border-edge text-content rounded px-2 py-1 text-[11px] cursor-pointer font-mono hover:bg-white/[0.08] disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
|
245
|
+
title="Restart this node"
|
|
246
|
+
>
|
|
247
|
+
<RotateCcw size={11} />
|
|
248
|
+
{actionLoading === 'restart' ? 'Restarting...' : 'Restart'}
|
|
249
|
+
</button>
|
|
250
|
+
<button
|
|
251
|
+
onClick={handleForceNext}
|
|
252
|
+
disabled={actionLoading !== null}
|
|
253
|
+
className="flex items-center gap-1 bg-transparent border border-accent/40 text-accent rounded px-2 py-1 text-[11px] cursor-pointer font-mono hover:bg-accent/10 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
|
254
|
+
title="Skip to next node"
|
|
255
|
+
>
|
|
256
|
+
<SkipForward size={11} />
|
|
257
|
+
{actionLoading === 'force-next' ? 'Skipping...' : 'Force Next'}
|
|
258
|
+
</button>
|
|
259
|
+
</div>
|
|
260
|
+
)}
|
|
261
|
+
</div>
|
|
262
|
+
<button
|
|
263
|
+
onClick={onClose}
|
|
264
|
+
className="shrink-0 p-1.5 rounded-lg border border-edge text-content-muted hover:border-content/20 hover:text-content cursor-pointer outline-none transition-colors"
|
|
265
|
+
>
|
|
266
|
+
<X size={16} />
|
|
267
|
+
</button>
|
|
268
|
+
</div>
|
|
269
|
+
|
|
270
|
+
{/* Content */}
|
|
271
|
+
<div className="flex-1 overflow-y-auto px-6 py-5 space-y-5">
|
|
272
|
+
{/* Timing */}
|
|
273
|
+
<div>
|
|
274
|
+
<SectionLabel>Timing</SectionLabel>
|
|
275
|
+
<div className="flex gap-4 text-[12px]">
|
|
276
|
+
{execution?.startedAt && (
|
|
277
|
+
<div className="rounded-2xl border border-edge px-4 py-2.5">
|
|
278
|
+
<div className="text-[10px] text-content-muted uppercase">Started</div>
|
|
279
|
+
<div className="text-content font-mono font-semibold">{formatTime(execution.startedAt)}</div>
|
|
280
|
+
</div>
|
|
281
|
+
)}
|
|
282
|
+
{execution?.completedAt && (
|
|
283
|
+
<div className="rounded-2xl border border-edge px-4 py-2.5">
|
|
284
|
+
<div className="text-[10px] text-content-muted uppercase">Duration</div>
|
|
285
|
+
<div className="text-content font-mono font-semibold">
|
|
286
|
+
{formatDuration(execution.startedAt, execution.completedAt)}
|
|
287
|
+
</div>
|
|
288
|
+
</div>
|
|
289
|
+
)}
|
|
290
|
+
{execution?.startedAt && !execution?.completedAt && statusLabel === 'running' && (
|
|
291
|
+
<div className="rounded-2xl border border-accent/30 px-4 py-2.5">
|
|
292
|
+
<div className="text-[10px] text-accent uppercase">Elapsed</div>
|
|
293
|
+
<div className="text-accent font-mono font-semibold animate-pulse">
|
|
294
|
+
{formatDuration(execution.startedAt, new Date().toISOString())}
|
|
295
|
+
</div>
|
|
296
|
+
</div>
|
|
297
|
+
)}
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
300
|
+
|
|
301
|
+
{/* Error */}
|
|
302
|
+
{execution?.error && (
|
|
303
|
+
<div>
|
|
304
|
+
<SectionLabel>Error</SectionLabel>
|
|
305
|
+
<div className="rounded-2xl bg-error/10 border border-error/20 p-4 text-[12px] text-error font-mono leading-relaxed break-all">
|
|
306
|
+
{execution.error}
|
|
307
|
+
</div>
|
|
308
|
+
</div>
|
|
309
|
+
)}
|
|
310
|
+
|
|
311
|
+
{/* Agent metrics */}
|
|
312
|
+
{isAgent && outputData && execution?.status === 'completed' && (
|
|
313
|
+
<div>
|
|
314
|
+
<SectionLabel>Agent Metrics</SectionLabel>
|
|
315
|
+
<div className="grid grid-cols-2 gap-2">
|
|
316
|
+
{outputData.costUsd != null && (
|
|
317
|
+
<MetricPill label="Cost" value={`$${outputData.costUsd.toFixed(4)}`} />
|
|
318
|
+
)}
|
|
319
|
+
{outputData.numTurns != null && (
|
|
320
|
+
<MetricPill label="Turns" value={`${outputData.numTurns}`} />
|
|
321
|
+
)}
|
|
322
|
+
{outputData.model && (
|
|
323
|
+
<MetricPill label="Model" value={outputData.model.replace(/^claude-/, '')} />
|
|
324
|
+
)}
|
|
325
|
+
{outputData.contextWindowPct != null && (
|
|
326
|
+
<MetricPill label="Context" value={formatContextWindow(outputData.contextWindowPct)} />
|
|
327
|
+
)}
|
|
328
|
+
{outputData.stopReason && (
|
|
329
|
+
<MetricPill label="Stop" value={outputData.stopReason} />
|
|
330
|
+
)}
|
|
331
|
+
{outputData.toolCalls?.total != null && (
|
|
332
|
+
<MetricPill label="Tool Calls" value={`${outputData.toolCalls.total}`} />
|
|
333
|
+
)}
|
|
334
|
+
</div>
|
|
335
|
+
</div>
|
|
336
|
+
)}
|
|
337
|
+
|
|
338
|
+
{/* Activity log (tool calls + assistant messages) */}
|
|
339
|
+
{isAgent && execution?.toolCalls && execution.toolCalls.length > 0 && (
|
|
340
|
+
<div>
|
|
341
|
+
<SectionLabel>Activity Log ({execution.toolCalls.length})</SectionLabel>
|
|
342
|
+
<div
|
|
343
|
+
ref={toolCallsRef}
|
|
344
|
+
className="max-h-[400px] overflow-y-auto rounded-2xl border border-edge bg-surface-alt"
|
|
345
|
+
>
|
|
346
|
+
{execution.toolCalls.map((tc: ToolCallEntry, i: number) => (
|
|
347
|
+
<ActivityRow key={tc.id} tc={tc} isFirst={i === 0} />
|
|
348
|
+
))}
|
|
349
|
+
</div>
|
|
350
|
+
</div>
|
|
351
|
+
)}
|
|
352
|
+
|
|
353
|
+
{/* Agent label for non-completed agents */}
|
|
354
|
+
{isAgent && statusLabel === 'running' && (!execution?.toolCalls || execution.toolCalls.length === 0) && (
|
|
355
|
+
<div className="text-[12px] text-content-muted italic">
|
|
356
|
+
Agent is starting up... tool calls will appear here as they happen.
|
|
357
|
+
</div>
|
|
358
|
+
)}
|
|
359
|
+
|
|
360
|
+
{/* Agent output */}
|
|
361
|
+
{isAgent && outputData?.output && (
|
|
362
|
+
<div>
|
|
363
|
+
<SectionLabel>Agent Output</SectionLabel>
|
|
364
|
+
<div className="max-h-[300px] overflow-y-auto rounded-2xl border border-edge bg-surface-alt p-4 text-[12px] font-mono text-content-secondary leading-relaxed whitespace-pre-wrap break-words">
|
|
365
|
+
{outputData.output}
|
|
366
|
+
</div>
|
|
367
|
+
</div>
|
|
368
|
+
)}
|
|
369
|
+
|
|
370
|
+
{/* Node info for non-agent nodes */}
|
|
371
|
+
{!isAgent && (
|
|
372
|
+
<div>
|
|
373
|
+
<SectionLabel>Node Info</SectionLabel>
|
|
374
|
+
<div className="rounded-2xl border border-edge bg-surface-alt px-4 py-3 text-[12px] text-content">
|
|
375
|
+
{getNodeLabel(nodeType, nodeData) || 'No additional details'}
|
|
376
|
+
</div>
|
|
377
|
+
</div>
|
|
378
|
+
)}
|
|
379
|
+
</div>
|
|
380
|
+
</div>
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function MetricPill({ label, value }: { label: string; value: string }) {
|
|
385
|
+
return (
|
|
386
|
+
<div className="rounded-2xl border border-edge px-4 py-2.5 text-center">
|
|
387
|
+
<div className="text-[10px] text-content-muted uppercase tracking-wider">{label}</div>
|
|
388
|
+
<div className="text-[12px] font-mono font-semibold text-content truncate">{value}</div>
|
|
389
|
+
</div>
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/* ── Time helpers ── */
|
|
394
|
+
|
|
395
|
+
function formatTime(iso: string): string {
|
|
396
|
+
return new Date(iso).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function formatTimeShort(iso: string): string {
|
|
400
|
+
return new Date(iso).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function formatDuration(start: string | null, end: string): string {
|
|
404
|
+
if (!start) return '\u2014';
|
|
405
|
+
const ms = new Date(end).getTime() - new Date(start).getTime();
|
|
406
|
+
if (ms < 1000) return `${ms}ms`;
|
|
407
|
+
const s = Math.floor(ms / 1000);
|
|
408
|
+
if (s < 60) return `${s}s`;
|
|
409
|
+
const m = Math.floor(s / 60);
|
|
410
|
+
const rs = s % 60;
|
|
411
|
+
if (m < 60) return `${m}m ${rs}s`;
|
|
412
|
+
const h = Math.floor(m / 60);
|
|
413
|
+
const rm = m % 60;
|
|
414
|
+
return `${h}h ${rm}m`;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/* ── Main Graph View ── */
|
|
418
|
+
|
|
419
|
+
function MonitorGraphView({
|
|
420
|
+
monitorData,
|
|
421
|
+
onNodeClick,
|
|
422
|
+
}: {
|
|
423
|
+
monitorData: WorkflowMonitorData;
|
|
424
|
+
onNodeClick: (nodeId: string) => void;
|
|
425
|
+
}) {
|
|
426
|
+
const nodeTypes = useMemo(() => MONITOR_NODE_TYPES, []);
|
|
427
|
+
|
|
428
|
+
// Convert graph data to React Flow nodes/edges with execution state
|
|
429
|
+
const nodes: Node[] = useMemo(() =>
|
|
430
|
+
monitorData.graphData.nodes.map(n => {
|
|
431
|
+
const exec = monitorData.nodeExecutions[n.id];
|
|
432
|
+
const isCurrent = n.id === monitorData.currentNodeId && monitorData.status === 'running';
|
|
433
|
+
const status = exec?.status || (isCurrent ? 'running' : 'pending');
|
|
434
|
+
return {
|
|
435
|
+
...n,
|
|
436
|
+
data: {
|
|
437
|
+
...n.data,
|
|
438
|
+
_execStatus: status,
|
|
439
|
+
_error: exec?.error || null,
|
|
440
|
+
_onNodeClick: onNodeClick,
|
|
441
|
+
},
|
|
442
|
+
};
|
|
443
|
+
}),
|
|
444
|
+
[monitorData.graphData.nodes, monitorData.nodeExecutions, monitorData.currentNodeId, monitorData.status, onNodeClick],
|
|
445
|
+
);
|
|
446
|
+
|
|
447
|
+
const edges: Edge[] = useMemo(
|
|
448
|
+
() => colorizeEdges(monitorData.graphData.edges.map(e => ({
|
|
449
|
+
...e,
|
|
450
|
+
type: 'smoothstep',
|
|
451
|
+
animated: monitorData.status === 'running',
|
|
452
|
+
}))),
|
|
453
|
+
[monitorData.graphData.edges, monitorData.status],
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
return (
|
|
457
|
+
<ReactFlow
|
|
458
|
+
nodes={nodes}
|
|
459
|
+
edges={edges}
|
|
460
|
+
nodeTypes={nodeTypes}
|
|
461
|
+
fitView
|
|
462
|
+
nodesDraggable={false}
|
|
463
|
+
nodesConnectable={false}
|
|
464
|
+
elementsSelectable
|
|
465
|
+
onNodeClick={(_, node) => onNodeClick(node.id)}
|
|
466
|
+
panOnDrag
|
|
467
|
+
zoomOnScroll
|
|
468
|
+
defaultEdgeOptions={{ type: 'smoothstep', animated: true }}
|
|
469
|
+
className="workflow-monitor-canvas"
|
|
470
|
+
>
|
|
471
|
+
<Background variant={BackgroundVariant.Dots} gap={16} size={1} color="var(--border-color)" />
|
|
472
|
+
<MiniMap
|
|
473
|
+
nodeStrokeColor="var(--accent)"
|
|
474
|
+
nodeColor="var(--bg-tertiary)"
|
|
475
|
+
maskColor="rgba(0,0,0,0.3)"
|
|
476
|
+
className="!bg-surface !border !border-edge !rounded-2xl"
|
|
477
|
+
/>
|
|
478
|
+
</ReactFlow>
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/* ── Modal ── */
|
|
483
|
+
|
|
484
|
+
export function WorkflowMonitorModal({ featureId, featureName, isOpen, onClose }: WorkflowMonitorModalProps) {
|
|
485
|
+
const [monitorData, setMonitorData] = useState<WorkflowMonitorData | null>(null);
|
|
486
|
+
const [selectedNodeId, setSelectedNodeId] = useState<string | null>(null);
|
|
487
|
+
const [loading, setLoading] = useState(true);
|
|
488
|
+
const pollerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
489
|
+
|
|
490
|
+
const fetchMonitor = useCallback(async () => {
|
|
491
|
+
try {
|
|
492
|
+
const { monitor } = await apiClient.getWorkflowMonitor(featureId);
|
|
493
|
+
setMonitorData(monitor);
|
|
494
|
+
setLoading(false);
|
|
495
|
+
return monitor;
|
|
496
|
+
} catch {
|
|
497
|
+
setLoading(false);
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
}, [featureId]);
|
|
501
|
+
|
|
502
|
+
// Initial fetch + polling
|
|
503
|
+
useEffect(() => {
|
|
504
|
+
if (!isOpen) return;
|
|
505
|
+
|
|
506
|
+
setLoading(true);
|
|
507
|
+
setSelectedNodeId(null);
|
|
508
|
+
fetchMonitor();
|
|
509
|
+
|
|
510
|
+
pollerRef.current = setInterval(async () => {
|
|
511
|
+
const data = await fetchMonitor();
|
|
512
|
+
// Stop polling when execution is done
|
|
513
|
+
if (data && ['completed', 'failed', 'idle'].includes(data.status)) {
|
|
514
|
+
if (pollerRef.current) {
|
|
515
|
+
clearInterval(pollerRef.current);
|
|
516
|
+
pollerRef.current = null;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}, 5000);
|
|
520
|
+
|
|
521
|
+
return () => {
|
|
522
|
+
if (pollerRef.current) {
|
|
523
|
+
clearInterval(pollerRef.current);
|
|
524
|
+
pollerRef.current = null;
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
}, [isOpen, fetchMonitor]);
|
|
528
|
+
|
|
529
|
+
// Close on Escape
|
|
530
|
+
useEffect(() => {
|
|
531
|
+
if (!isOpen) return;
|
|
532
|
+
const handleKey = (e: KeyboardEvent) => {
|
|
533
|
+
if (e.key === 'Escape') onClose();
|
|
534
|
+
};
|
|
535
|
+
document.addEventListener('keydown', handleKey);
|
|
536
|
+
return () => document.removeEventListener('keydown', handleKey);
|
|
537
|
+
}, [isOpen, onClose]);
|
|
538
|
+
|
|
539
|
+
// Restart polling after an action (force-next/restart) re-activates the workflow
|
|
540
|
+
const ensurePolling = useCallback(() => {
|
|
541
|
+
fetchMonitor();
|
|
542
|
+
if (!pollerRef.current) {
|
|
543
|
+
pollerRef.current = setInterval(async () => {
|
|
544
|
+
const data = await fetchMonitor();
|
|
545
|
+
if (data && ['completed', 'failed', 'idle'].includes(data.status)) {
|
|
546
|
+
if (pollerRef.current) {
|
|
547
|
+
clearInterval(pollerRef.current);
|
|
548
|
+
pollerRef.current = null;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}, 5000);
|
|
552
|
+
}
|
|
553
|
+
}, [fetchMonitor]);
|
|
554
|
+
|
|
555
|
+
const handleNodeClick = useCallback((nodeId: string) => {
|
|
556
|
+
setSelectedNodeId(prev => prev === nodeId ? null : nodeId);
|
|
557
|
+
}, []);
|
|
558
|
+
|
|
559
|
+
if (!isOpen) return null;
|
|
560
|
+
|
|
561
|
+
const selectedNode = monitorData?.graphData.nodes.find(n => n.id === selectedNodeId);
|
|
562
|
+
const selectedExec = selectedNodeId ? monitorData?.nodeExecutions[selectedNodeId] || null : null;
|
|
563
|
+
|
|
564
|
+
return (
|
|
565
|
+
<div className="fixed inset-0 z-[300]">
|
|
566
|
+
{/* Modal — full screen */}
|
|
567
|
+
<div className="flex flex-col w-screen h-screen bg-surface text-content">
|
|
568
|
+
{/* Header */}
|
|
569
|
+
<div className="flex items-center justify-between px-6 py-4 border-b border-edge bg-surface-alt">
|
|
570
|
+
<div className="flex items-center gap-3 min-w-0">
|
|
571
|
+
<span className="text-[14px] font-bold uppercase tracking-widest text-content">
|
|
572
|
+
Workflow Monitor
|
|
573
|
+
</span>
|
|
574
|
+
<span className="text-[12px] font-mono text-accent">{featureId}</span>
|
|
575
|
+
<span className="text-[12px] text-content-muted truncate">{featureName}</span>
|
|
576
|
+
{monitorData && <StatusBadge status={monitorData.status} />}
|
|
577
|
+
</div>
|
|
578
|
+
<button
|
|
579
|
+
onClick={onClose}
|
|
580
|
+
className="shrink-0 p-1.5 rounded-lg border border-edge text-content-muted hover:border-content/20 hover:text-content cursor-pointer outline-none transition-colors"
|
|
581
|
+
>
|
|
582
|
+
<X size={18} />
|
|
583
|
+
</button>
|
|
584
|
+
</div>
|
|
585
|
+
|
|
586
|
+
{/* Body */}
|
|
587
|
+
<div className="flex flex-1 overflow-hidden">
|
|
588
|
+
{loading && !monitorData && (
|
|
589
|
+
<div className="flex-1 flex items-center justify-center text-[13px] text-content-muted">
|
|
590
|
+
Loading execution data...
|
|
591
|
+
</div>
|
|
592
|
+
)}
|
|
593
|
+
|
|
594
|
+
{!loading && !monitorData && (
|
|
595
|
+
<div className="flex-1 flex items-center justify-center text-[13px] text-content-muted">
|
|
596
|
+
No workflow execution found for this feature.
|
|
597
|
+
</div>
|
|
598
|
+
)}
|
|
599
|
+
|
|
600
|
+
{monitorData && (
|
|
601
|
+
<>
|
|
602
|
+
{/* Graph */}
|
|
603
|
+
<div className="flex-1 relative">
|
|
604
|
+
<ReactFlowProvider>
|
|
605
|
+
<MonitorGraphView monitorData={monitorData} onNodeClick={handleNodeClick} />
|
|
606
|
+
</ReactFlowProvider>
|
|
607
|
+
{/* Hint overlay when no node selected */}
|
|
608
|
+
{!selectedNodeId && (
|
|
609
|
+
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 px-4 py-2 rounded-2xl bg-surface-alt/90 border border-edge text-[11px] text-content-muted backdrop-blur-sm pointer-events-none">
|
|
610
|
+
Click a node to view execution details and tool usage
|
|
611
|
+
</div>
|
|
612
|
+
)}
|
|
613
|
+
</div>
|
|
614
|
+
|
|
615
|
+
{/* Side panel */}
|
|
616
|
+
{selectedNode && (
|
|
617
|
+
<NodeDetailPanel
|
|
618
|
+
nodeId={selectedNodeId!}
|
|
619
|
+
nodeType={selectedNode.type}
|
|
620
|
+
nodeData={selectedNode.data}
|
|
621
|
+
execution={selectedExec}
|
|
622
|
+
isCurrentNode={selectedNodeId === monitorData.currentNodeId}
|
|
623
|
+
featureId={featureId}
|
|
624
|
+
onClose={() => setSelectedNodeId(null)}
|
|
625
|
+
onAction={ensurePolling}
|
|
626
|
+
/>
|
|
627
|
+
)}
|
|
628
|
+
</>
|
|
629
|
+
)}
|
|
630
|
+
</div>
|
|
631
|
+
</div>
|
|
632
|
+
</div>
|
|
633
|
+
);
|
|
634
|
+
}
|