@marktoflow/gui 2.0.0-alpha.1
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/.turbo/turbo-build.log +26 -0
- package/.turbo/turbo-test.log +22 -0
- package/README.md +179 -0
- package/dist/client/assets/index-DwTI8opO.js +608 -0
- package/dist/client/assets/index-DwTI8opO.js.map +1 -0
- package/dist/client/assets/index-RoEdL6gO.css +1 -0
- package/dist/client/index.html +20 -0
- package/dist/client/vite.svg +9 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +56 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/routes/ai.js +50 -0
- package/dist/server/routes/ai.js.map +1 -0
- package/dist/server/routes/execute.js +62 -0
- package/dist/server/routes/execute.js.map +1 -0
- package/dist/server/routes/workflows.js +99 -0
- package/dist/server/routes/workflows.js.map +1 -0
- package/dist/server/server/index.js +95 -0
- package/dist/server/server/index.js.map +1 -0
- package/dist/server/server/routes/ai.js +87 -0
- package/dist/server/server/routes/ai.js.map +1 -0
- package/dist/server/server/routes/execute.js +63 -0
- package/dist/server/server/routes/execute.js.map +1 -0
- package/dist/server/server/routes/tools.js +518 -0
- package/dist/server/server/routes/tools.js.map +1 -0
- package/dist/server/server/routes/workflows.js +99 -0
- package/dist/server/server/routes/workflows.js.map +1 -0
- package/dist/server/server/services/AIService.js +69 -0
- package/dist/server/server/services/AIService.js.map +1 -0
- package/dist/server/server/services/FileWatcher.js +60 -0
- package/dist/server/server/services/FileWatcher.js.map +1 -0
- package/dist/server/server/services/WorkflowService.js +363 -0
- package/dist/server/server/services/WorkflowService.js.map +1 -0
- package/dist/server/server/services/agents/claude-code-provider.js +250 -0
- package/dist/server/server/services/agents/claude-code-provider.js.map +1 -0
- package/dist/server/server/services/agents/claude-provider.js +204 -0
- package/dist/server/server/services/agents/claude-provider.js.map +1 -0
- package/dist/server/server/services/agents/copilot-provider.js +227 -0
- package/dist/server/server/services/agents/copilot-provider.js.map +1 -0
- package/dist/server/server/services/agents/demo-provider.js +167 -0
- package/dist/server/server/services/agents/demo-provider.js.map +1 -0
- package/dist/server/server/services/agents/index.js +31 -0
- package/dist/server/server/services/agents/index.js.map +1 -0
- package/dist/server/server/services/agents/ollama-provider.js +220 -0
- package/dist/server/server/services/agents/ollama-provider.js.map +1 -0
- package/dist/server/server/services/agents/prompts.js +436 -0
- package/dist/server/server/services/agents/prompts.js.map +1 -0
- package/dist/server/server/services/agents/registry.js +242 -0
- package/dist/server/server/services/agents/registry.js.map +1 -0
- package/dist/server/server/services/agents/types.js +6 -0
- package/dist/server/server/services/agents/types.js.map +1 -0
- package/dist/server/server/websocket/index.js +85 -0
- package/dist/server/server/websocket/index.js.map +1 -0
- package/dist/server/services/AIService.d.ts +30 -0
- package/dist/server/services/AIService.d.ts.map +1 -0
- package/dist/server/services/AIService.js +216 -0
- package/dist/server/services/AIService.js.map +1 -0
- package/dist/server/services/FileWatcher.d.ts +10 -0
- package/dist/server/services/FileWatcher.d.ts.map +1 -0
- package/dist/server/services/FileWatcher.js +62 -0
- package/dist/server/services/FileWatcher.js.map +1 -0
- package/dist/server/services/WorkflowService.d.ts +54 -0
- package/dist/server/services/WorkflowService.d.ts.map +1 -0
- package/dist/server/services/WorkflowService.js +323 -0
- package/dist/server/services/WorkflowService.js.map +1 -0
- package/dist/server/shared/constants.js +175 -0
- package/dist/server/shared/constants.js.map +1 -0
- package/dist/server/shared/types.js +3 -0
- package/dist/server/shared/types.js.map +1 -0
- package/dist/server/websocket/index.d.ts +10 -0
- package/dist/server/websocket/index.d.ts.map +1 -0
- package/dist/server/websocket/index.js +85 -0
- package/dist/server/websocket/index.js.map +1 -0
- package/index.html +19 -0
- package/package.json +96 -0
- package/playwright.config.ts +27 -0
- package/postcss.config.js +6 -0
- package/public/vite.svg +9 -0
- package/src/client/App.tsx +520 -0
- package/src/client/components/Canvas/Canvas.tsx +405 -0
- package/src/client/components/Canvas/ExecutionOverlay.tsx +847 -0
- package/src/client/components/Canvas/NodeContextMenu.tsx +188 -0
- package/src/client/components/Canvas/OutputNode.tsx +111 -0
- package/src/client/components/Canvas/StepNode.tsx +106 -0
- package/src/client/components/Canvas/SubWorkflowNode.tsx +141 -0
- package/src/client/components/Canvas/Toolbar.tsx +189 -0
- package/src/client/components/Canvas/TriggerNode.tsx +128 -0
- package/src/client/components/Editor/InputsEditor.tsx +458 -0
- package/src/client/components/Editor/NewStepWizard.tsx +344 -0
- package/src/client/components/Editor/StepEditor.tsx +532 -0
- package/src/client/components/Editor/YamlEditor.tsx +160 -0
- package/src/client/components/Panels/PropertiesPanel.tsx +589 -0
- package/src/client/components/Prompt/ChangePreview.tsx +281 -0
- package/src/client/components/Prompt/PromptHistoryPanel.tsx +209 -0
- package/src/client/components/Prompt/PromptInput.tsx +108 -0
- package/src/client/components/Sidebar/Sidebar.tsx +343 -0
- package/src/client/components/common/Breadcrumb.tsx +40 -0
- package/src/client/components/common/Button.tsx +68 -0
- package/src/client/components/common/ContextMenu.tsx +202 -0
- package/src/client/components/common/KeyboardShortcuts.tsx +143 -0
- package/src/client/components/common/Modal.tsx +93 -0
- package/src/client/components/common/Tabs.tsx +57 -0
- package/src/client/components/common/ThemeToggle.tsx +63 -0
- package/src/client/components/index.ts +32 -0
- package/src/client/hooks/index.ts +4 -0
- package/src/client/hooks/useAIPrompt.ts +108 -0
- package/src/client/hooks/useCanvas.ts +247 -0
- package/src/client/hooks/useWebSocket.ts +164 -0
- package/src/client/hooks/useWorkflow.ts +138 -0
- package/src/client/main.tsx +10 -0
- package/src/client/stores/canvasStore.ts +348 -0
- package/src/client/stores/editorStore.ts +133 -0
- package/src/client/stores/executionStore.ts +440 -0
- package/src/client/stores/index.ts +4 -0
- package/src/client/stores/layoutStore.ts +103 -0
- package/src/client/stores/navigationStore.ts +49 -0
- package/src/client/stores/promptStore.ts +113 -0
- package/src/client/stores/themeStore.ts +75 -0
- package/src/client/stores/workflowStore.ts +177 -0
- package/src/client/styles/globals.css +346 -0
- package/src/client/utils/cn.ts +9 -0
- package/src/client/utils/index.ts +4 -0
- package/src/client/utils/serviceIcons.tsx +64 -0
- package/src/client/utils/stepValidation.ts +155 -0
- package/src/client/utils/workflowToGraph.ts +299 -0
- package/src/server/index.ts +114 -0
- package/src/server/routes/ai.ts +91 -0
- package/src/server/routes/execute.ts +71 -0
- package/src/server/routes/tools.ts +564 -0
- package/src/server/routes/workflows.ts +106 -0
- package/src/server/services/AIService.ts +105 -0
- package/src/server/services/FileWatcher.ts +69 -0
- package/src/server/services/WorkflowService.ts +441 -0
- package/src/server/services/agents/claude-code-provider.ts +320 -0
- package/src/server/services/agents/claude-provider.ts +248 -0
- package/src/server/services/agents/copilot-provider.ts +311 -0
- package/src/server/services/agents/demo-provider.ts +184 -0
- package/src/server/services/agents/index.ts +31 -0
- package/src/server/services/agents/ollama-provider.ts +267 -0
- package/src/server/services/agents/prompts.ts +482 -0
- package/src/server/services/agents/registry.ts +289 -0
- package/src/server/services/agents/types.ts +146 -0
- package/src/server/websocket/index.ts +104 -0
- package/src/shared/constants.ts +180 -0
- package/src/shared/types.ts +179 -0
- package/tailwind.config.ts +73 -0
- package/tests/e2e/app.spec.ts +90 -0
- package/tests/e2e/canvas.spec.ts +128 -0
- package/tests/e2e/workflow.spec.ts +185 -0
- package/tests/integration/api.test.ts +250 -0
- package/tests/integration/testApp.ts +31 -0
- package/tests/setup.ts +37 -0
- package/tests/unit/canvasStore.test.ts +502 -0
- package/tests/unit/components.test.tsx +151 -0
- package/tests/unit/executionStore.test.ts +527 -0
- package/tests/unit/layoutStore.test.ts +194 -0
- package/tests/unit/navigationStore.test.ts +152 -0
- package/tests/unit/stepValidation.test.ts +226 -0
- package/tests/unit/themeStore.test.ts +141 -0
- package/tests/unit/workflowToGraph.test.ts +289 -0
- package/tsconfig.json +29 -0
- package/tsconfig.server.json +28 -0
- package/vite.config.ts +31 -0
- package/vitest.config.ts +26 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { useState, useCallback, useEffect } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
FileText,
|
|
4
|
+
FolderTree,
|
|
5
|
+
ChevronRight,
|
|
6
|
+
ChevronLeft,
|
|
7
|
+
Plus,
|
|
8
|
+
Search,
|
|
9
|
+
Loader2,
|
|
10
|
+
X,
|
|
11
|
+
} from 'lucide-react';
|
|
12
|
+
import { useWorkflowStore } from '../../stores/workflowStore';
|
|
13
|
+
import { useNavigationStore } from '../../stores/navigationStore';
|
|
14
|
+
import { useLayoutStore } from '../../stores/layoutStore';
|
|
15
|
+
|
|
16
|
+
export function Sidebar() {
|
|
17
|
+
const [activeTab, setActiveTab] = useState<'workflows' | 'tools'>(
|
|
18
|
+
'workflows'
|
|
19
|
+
);
|
|
20
|
+
const { workflows, selectedWorkflow, selectWorkflow } = useWorkflowStore();
|
|
21
|
+
const { resetNavigation } = useNavigationStore();
|
|
22
|
+
const { sidebarOpen, setSidebarOpen, breakpoint } = useLayoutStore();
|
|
23
|
+
|
|
24
|
+
// Handle workflow selection - resets sub-workflow navigation
|
|
25
|
+
const handleSelectWorkflow = useCallback(
|
|
26
|
+
(path: string) => {
|
|
27
|
+
resetNavigation();
|
|
28
|
+
selectWorkflow(path);
|
|
29
|
+
// Close sidebar on mobile after selection
|
|
30
|
+
if (breakpoint === 'mobile') {
|
|
31
|
+
setSidebarOpen(false);
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
[resetNavigation, selectWorkflow, breakpoint, setSidebarOpen]
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
// Collapsed state for desktop
|
|
38
|
+
if (!sidebarOpen && breakpoint !== 'mobile') {
|
|
39
|
+
return (
|
|
40
|
+
<button
|
|
41
|
+
onClick={() => setSidebarOpen(true)}
|
|
42
|
+
className="w-12 bg-panel-bg border-r border-node-border flex flex-col items-center py-4 gap-4 hover:bg-white/5 transition-colors"
|
|
43
|
+
aria-label="Expand sidebar"
|
|
44
|
+
>
|
|
45
|
+
<ChevronRight className="w-4 h-4 text-gray-400" />
|
|
46
|
+
<FolderTree className="w-5 h-5 text-primary" />
|
|
47
|
+
</button>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Mobile overlay
|
|
52
|
+
if (breakpoint === 'mobile') {
|
|
53
|
+
if (!sidebarOpen) return null;
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<>
|
|
57
|
+
{/* Backdrop */}
|
|
58
|
+
<div
|
|
59
|
+
className="fixed inset-0 bg-black/50 z-40 md:hidden"
|
|
60
|
+
onClick={() => setSidebarOpen(false)}
|
|
61
|
+
/>
|
|
62
|
+
{/* Sidebar */}
|
|
63
|
+
<div className="fixed inset-y-0 left-0 w-72 bg-panel-bg border-r border-node-border flex flex-col z-50 md:hidden animate-slide-in-left">
|
|
64
|
+
<SidebarContent
|
|
65
|
+
activeTab={activeTab}
|
|
66
|
+
setActiveTab={setActiveTab}
|
|
67
|
+
workflows={workflows}
|
|
68
|
+
selectedWorkflow={selectedWorkflow}
|
|
69
|
+
onSelectWorkflow={handleSelectWorkflow}
|
|
70
|
+
onClose={() => setSidebarOpen(false)}
|
|
71
|
+
showClose
|
|
72
|
+
/>
|
|
73
|
+
</div>
|
|
74
|
+
</>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Desktop/Tablet sidebar
|
|
79
|
+
return (
|
|
80
|
+
<div className="w-64 bg-panel-bg border-r border-node-border flex flex-col">
|
|
81
|
+
<SidebarContent
|
|
82
|
+
activeTab={activeTab}
|
|
83
|
+
setActiveTab={setActiveTab}
|
|
84
|
+
workflows={workflows}
|
|
85
|
+
selectedWorkflow={selectedWorkflow}
|
|
86
|
+
onSelectWorkflow={handleSelectWorkflow}
|
|
87
|
+
onClose={() => setSidebarOpen(false)}
|
|
88
|
+
showClose={breakpoint === 'tablet'}
|
|
89
|
+
/>
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
interface SidebarContentProps {
|
|
95
|
+
activeTab: 'workflows' | 'tools';
|
|
96
|
+
setActiveTab: (tab: 'workflows' | 'tools') => void;
|
|
97
|
+
workflows: Array<{ path: string; name: string }>;
|
|
98
|
+
selectedWorkflow: string | null;
|
|
99
|
+
onSelectWorkflow: (path: string) => void;
|
|
100
|
+
onClose: () => void;
|
|
101
|
+
showClose?: boolean;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function SidebarContent({
|
|
105
|
+
activeTab,
|
|
106
|
+
setActiveTab,
|
|
107
|
+
workflows,
|
|
108
|
+
selectedWorkflow,
|
|
109
|
+
onSelectWorkflow,
|
|
110
|
+
onClose,
|
|
111
|
+
showClose,
|
|
112
|
+
}: SidebarContentProps) {
|
|
113
|
+
return (
|
|
114
|
+
<>
|
|
115
|
+
{/* Logo/Title */}
|
|
116
|
+
<div className="p-4 border-b border-node-border flex items-center justify-between">
|
|
117
|
+
<h1 className="text-lg font-semibold text-white flex items-center gap-2">
|
|
118
|
+
<FolderTree className="w-5 h-5 text-primary" />
|
|
119
|
+
Marktoflow
|
|
120
|
+
</h1>
|
|
121
|
+
{showClose && (
|
|
122
|
+
<button
|
|
123
|
+
onClick={onClose}
|
|
124
|
+
className="w-8 h-8 rounded-lg flex items-center justify-center hover:bg-white/10 transition-colors"
|
|
125
|
+
aria-label="Close sidebar"
|
|
126
|
+
>
|
|
127
|
+
<X className="w-4 h-4 text-gray-400" />
|
|
128
|
+
</button>
|
|
129
|
+
)}
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
{/* Tab buttons */}
|
|
133
|
+
<div className="flex border-b border-node-border">
|
|
134
|
+
<button
|
|
135
|
+
onClick={() => setActiveTab('workflows')}
|
|
136
|
+
className={`flex-1 px-4 py-2 text-sm font-medium transition-colors ${
|
|
137
|
+
activeTab === 'workflows'
|
|
138
|
+
? 'text-primary border-b-2 border-primary'
|
|
139
|
+
: 'text-gray-400 hover:text-white'
|
|
140
|
+
}`}
|
|
141
|
+
>
|
|
142
|
+
Workflows
|
|
143
|
+
</button>
|
|
144
|
+
<button
|
|
145
|
+
onClick={() => setActiveTab('tools')}
|
|
146
|
+
className={`flex-1 px-4 py-2 text-sm font-medium transition-colors ${
|
|
147
|
+
activeTab === 'tools'
|
|
148
|
+
? 'text-primary border-b-2 border-primary'
|
|
149
|
+
: 'text-gray-400 hover:text-white'
|
|
150
|
+
}`}
|
|
151
|
+
>
|
|
152
|
+
Tools
|
|
153
|
+
</button>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
{/* Search */}
|
|
157
|
+
<div className="p-3 border-b border-node-border">
|
|
158
|
+
<div className="relative">
|
|
159
|
+
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-500" />
|
|
160
|
+
<input
|
|
161
|
+
type="text"
|
|
162
|
+
placeholder={`Search ${activeTab}...`}
|
|
163
|
+
className="w-full pl-9 pr-3 py-2 bg-node-bg border border-node-border rounded-lg text-sm text-white placeholder-gray-500 focus:outline-none focus:border-primary"
|
|
164
|
+
/>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
{/* Content */}
|
|
169
|
+
<div className="flex-1 overflow-y-auto p-2">
|
|
170
|
+
{activeTab === 'workflows' ? (
|
|
171
|
+
<WorkflowList
|
|
172
|
+
workflows={workflows}
|
|
173
|
+
selectedWorkflow={selectedWorkflow}
|
|
174
|
+
onSelect={onSelectWorkflow}
|
|
175
|
+
/>
|
|
176
|
+
) : (
|
|
177
|
+
<ToolsPalette />
|
|
178
|
+
)}
|
|
179
|
+
</div>
|
|
180
|
+
|
|
181
|
+
{/* New workflow button */}
|
|
182
|
+
{activeTab === 'workflows' && (
|
|
183
|
+
<div className="p-3 border-t border-node-border">
|
|
184
|
+
<button className="w-full flex items-center justify-center gap-2 px-4 py-2 bg-primary hover:bg-primary-dark text-white rounded-lg text-sm font-medium transition-colors">
|
|
185
|
+
<Plus className="w-4 h-4" />
|
|
186
|
+
New Workflow
|
|
187
|
+
</button>
|
|
188
|
+
</div>
|
|
189
|
+
)}
|
|
190
|
+
</>
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
interface WorkflowListProps {
|
|
195
|
+
workflows: Array<{ path: string; name: string }>;
|
|
196
|
+
selectedWorkflow: string | null;
|
|
197
|
+
onSelect: (path: string) => void;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function WorkflowList({
|
|
201
|
+
workflows,
|
|
202
|
+
selectedWorkflow,
|
|
203
|
+
onSelect,
|
|
204
|
+
}: WorkflowListProps) {
|
|
205
|
+
if (workflows.length === 0) {
|
|
206
|
+
return (
|
|
207
|
+
<div className="text-center py-8 text-gray-500 text-sm">
|
|
208
|
+
No workflows found
|
|
209
|
+
</div>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return (
|
|
214
|
+
<div className="space-y-1">
|
|
215
|
+
{workflows.map((workflow) => (
|
|
216
|
+
<button
|
|
217
|
+
key={workflow.path}
|
|
218
|
+
onClick={() => onSelect(workflow.path)}
|
|
219
|
+
className={`w-full flex items-center gap-2 px-3 py-2 rounded-lg text-left transition-colors ${
|
|
220
|
+
selectedWorkflow === workflow.path
|
|
221
|
+
? 'bg-primary/10 text-primary'
|
|
222
|
+
: 'text-gray-300 hover:bg-white/5'
|
|
223
|
+
}`}
|
|
224
|
+
>
|
|
225
|
+
<FileText className="w-4 h-4 flex-shrink-0" />
|
|
226
|
+
<span className="text-sm truncate">{workflow.name}</span>
|
|
227
|
+
<ChevronRight className="w-4 h-4 ml-auto flex-shrink-0 opacity-50" />
|
|
228
|
+
</button>
|
|
229
|
+
))}
|
|
230
|
+
</div>
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export interface ToolDefinition {
|
|
235
|
+
id: string;
|
|
236
|
+
name: string;
|
|
237
|
+
icon: string;
|
|
238
|
+
category: string;
|
|
239
|
+
description?: string;
|
|
240
|
+
sdk?: string;
|
|
241
|
+
authType?: string;
|
|
242
|
+
actionCount?: number;
|
|
243
|
+
actions?: string[];
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Fallback tools in case API is unavailable
|
|
247
|
+
const fallbackTools: ToolDefinition[] = [
|
|
248
|
+
{ id: 'slack', name: 'Slack', icon: '💬', category: 'Communication', sdk: '@slack/web-api' },
|
|
249
|
+
{ id: 'github', name: 'GitHub', icon: '🐙', category: 'Development', sdk: '@octokit/rest' },
|
|
250
|
+
{ id: 'jira', name: 'Jira', icon: '📋', category: 'Project Management', sdk: 'jira.js' },
|
|
251
|
+
{ id: 'gmail', name: 'Gmail', icon: '📧', category: 'Communication', sdk: 'googleapis' },
|
|
252
|
+
{ id: 'http', name: 'HTTP', icon: '🌐', category: 'Network' },
|
|
253
|
+
{ id: 'claude', name: 'Claude', icon: '🤖', category: 'AI' },
|
|
254
|
+
];
|
|
255
|
+
|
|
256
|
+
function ToolsPalette() {
|
|
257
|
+
const [tools, setTools] = useState<ToolDefinition[]>(fallbackTools);
|
|
258
|
+
const [loading, setLoading] = useState(true);
|
|
259
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
260
|
+
|
|
261
|
+
// Fetch tools from API
|
|
262
|
+
useEffect(() => {
|
|
263
|
+
async function fetchTools() {
|
|
264
|
+
try {
|
|
265
|
+
const response = await fetch('/api/tools');
|
|
266
|
+
if (response.ok) {
|
|
267
|
+
const data = await response.json();
|
|
268
|
+
setTools(data.tools);
|
|
269
|
+
}
|
|
270
|
+
} catch (error) {
|
|
271
|
+
console.error('Failed to fetch tools:', error);
|
|
272
|
+
// Keep fallback tools
|
|
273
|
+
} finally {
|
|
274
|
+
setLoading(false);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
fetchTools();
|
|
278
|
+
}, []);
|
|
279
|
+
|
|
280
|
+
// Filter tools by search query
|
|
281
|
+
const filteredTools = searchQuery
|
|
282
|
+
? tools.filter(
|
|
283
|
+
(t) =>
|
|
284
|
+
t.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
285
|
+
t.category.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
286
|
+
t.description?.toLowerCase().includes(searchQuery.toLowerCase())
|
|
287
|
+
)
|
|
288
|
+
: tools;
|
|
289
|
+
|
|
290
|
+
const categories = [...new Set(filteredTools.map((t) => t.category))];
|
|
291
|
+
|
|
292
|
+
const handleDragStart = (e: React.DragEvent, tool: ToolDefinition) => {
|
|
293
|
+
e.dataTransfer.setData('application/marktoflow-tool', JSON.stringify(tool));
|
|
294
|
+
e.dataTransfer.effectAllowed = 'copy';
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
if (loading) {
|
|
298
|
+
return (
|
|
299
|
+
<div className="flex items-center justify-center py-8">
|
|
300
|
+
<Loader2 className="w-5 h-5 text-gray-500 animate-spin" />
|
|
301
|
+
</div>
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return (
|
|
306
|
+
<div className="space-y-4">
|
|
307
|
+
{categories.map((category) => (
|
|
308
|
+
<div key={category}>
|
|
309
|
+
<h3 className="text-xs font-medium text-gray-500 uppercase tracking-wider px-2 mb-2">
|
|
310
|
+
{category}
|
|
311
|
+
</h3>
|
|
312
|
+
<div className="space-y-1">
|
|
313
|
+
{filteredTools
|
|
314
|
+
.filter((t) => t.category === category)
|
|
315
|
+
.map((tool) => (
|
|
316
|
+
<div
|
|
317
|
+
key={tool.id}
|
|
318
|
+
draggable
|
|
319
|
+
onDragStart={(e) => handleDragStart(e, tool)}
|
|
320
|
+
className="flex items-center gap-2 px-3 py-2 rounded-lg text-gray-300 hover:bg-white/5 cursor-grab active:cursor-grabbing transition-colors group"
|
|
321
|
+
title={tool.description || (tool.sdk ? 'SDK: ' + tool.sdk : undefined)}
|
|
322
|
+
>
|
|
323
|
+
<span className="text-lg">{tool.icon}</span>
|
|
324
|
+
<div className="flex-1 min-w-0">
|
|
325
|
+
<span className="text-sm block truncate">{tool.name}</span>
|
|
326
|
+
{tool.actionCount !== undefined && (
|
|
327
|
+
<span className="text-xs text-gray-500">{tool.actionCount} actions</span>
|
|
328
|
+
)}
|
|
329
|
+
</div>
|
|
330
|
+
</div>
|
|
331
|
+
))}
|
|
332
|
+
</div>
|
|
333
|
+
</div>
|
|
334
|
+
))}
|
|
335
|
+
|
|
336
|
+
{filteredTools.length === 0 && (
|
|
337
|
+
<div className="text-center py-8 text-gray-500 text-sm">
|
|
338
|
+
No tools found
|
|
339
|
+
</div>
|
|
340
|
+
)}
|
|
341
|
+
</div>
|
|
342
|
+
);
|
|
343
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ChevronRight, Home } from 'lucide-react';
|
|
2
|
+
|
|
3
|
+
export interface BreadcrumbItem {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
path?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface BreadcrumbProps {
|
|
10
|
+
items: BreadcrumbItem[];
|
|
11
|
+
onNavigate: (item: BreadcrumbItem, index: number) => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function Breadcrumb({ items, onNavigate }: BreadcrumbProps) {
|
|
15
|
+
if (items.length <= 1) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<nav className="flex items-center gap-1 px-4 py-2 bg-panel-bg border-b border-node-border">
|
|
21
|
+
{items.map((item, index) => (
|
|
22
|
+
<div key={item.id} className="flex items-center">
|
|
23
|
+
{index > 0 && <ChevronRight className="w-4 h-4 text-gray-600 mx-1" />}
|
|
24
|
+
<button
|
|
25
|
+
onClick={() => onNavigate(item, index)}
|
|
26
|
+
className={`flex items-center gap-1.5 px-2 py-1 rounded text-sm transition-colors ${
|
|
27
|
+
index === items.length - 1
|
|
28
|
+
? 'text-white font-medium cursor-default'
|
|
29
|
+
: 'text-gray-400 hover:text-white hover:bg-white/5'
|
|
30
|
+
}`}
|
|
31
|
+
disabled={index === items.length - 1}
|
|
32
|
+
>
|
|
33
|
+
{index === 0 && <Home className="w-3.5 h-3.5" />}
|
|
34
|
+
<span className="max-w-[150px] truncate">{item.name}</span>
|
|
35
|
+
</button>
|
|
36
|
+
</div>
|
|
37
|
+
))}
|
|
38
|
+
</nav>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import { cn } from '../../utils/cn';
|
|
3
|
+
import { Loader2 } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
export interface ButtonProps
|
|
6
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
7
|
+
variant?: 'primary' | 'secondary' | 'ghost' | 'destructive';
|
|
8
|
+
size?: 'sm' | 'md' | 'lg';
|
|
9
|
+
loading?: boolean;
|
|
10
|
+
icon?: React.ReactNode;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const variantClasses = {
|
|
14
|
+
primary:
|
|
15
|
+
'bg-primary hover:bg-primary-dark text-white border-transparent',
|
|
16
|
+
secondary:
|
|
17
|
+
'bg-node-bg hover:bg-white/10 text-gray-300 border-node-border hover:border-primary',
|
|
18
|
+
ghost: 'bg-transparent hover:bg-white/10 text-gray-300 border-transparent',
|
|
19
|
+
destructive:
|
|
20
|
+
'bg-error/10 hover:bg-error/20 text-error border-error/20 hover:border-error',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const sizeClasses = {
|
|
24
|
+
sm: 'px-2.5 py-1.5 text-xs',
|
|
25
|
+
md: 'px-4 py-2 text-sm',
|
|
26
|
+
lg: 'px-6 py-3 text-base',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
30
|
+
(
|
|
31
|
+
{
|
|
32
|
+
className,
|
|
33
|
+
variant = 'primary',
|
|
34
|
+
size = 'md',
|
|
35
|
+
loading,
|
|
36
|
+
icon,
|
|
37
|
+
disabled,
|
|
38
|
+
children,
|
|
39
|
+
...props
|
|
40
|
+
},
|
|
41
|
+
ref
|
|
42
|
+
) => {
|
|
43
|
+
return (
|
|
44
|
+
<button
|
|
45
|
+
ref={ref}
|
|
46
|
+
disabled={disabled || loading}
|
|
47
|
+
className={cn(
|
|
48
|
+
'inline-flex items-center justify-center gap-2 rounded-lg border font-medium transition-colors',
|
|
49
|
+
'focus:outline-none focus:ring-2 focus:ring-primary/50 focus:ring-offset-2 focus:ring-offset-panel-bg',
|
|
50
|
+
'disabled:opacity-50 disabled:cursor-not-allowed',
|
|
51
|
+
variantClasses[variant],
|
|
52
|
+
sizeClasses[size],
|
|
53
|
+
className
|
|
54
|
+
)}
|
|
55
|
+
{...props}
|
|
56
|
+
>
|
|
57
|
+
{loading ? (
|
|
58
|
+
<Loader2 className="w-4 h-4 animate-spin" />
|
|
59
|
+
) : (
|
|
60
|
+
icon && <span className="flex-shrink-0">{icon}</span>
|
|
61
|
+
)}
|
|
62
|
+
{children}
|
|
63
|
+
</button>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
Button.displayName = 'Button';
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import * as ContextMenuPrimitive from '@radix-ui/react-context-menu';
|
|
2
|
+
import { forwardRef } from 'react';
|
|
3
|
+
import { cn } from '../../utils/cn';
|
|
4
|
+
|
|
5
|
+
const ContextMenu = ContextMenuPrimitive.Root;
|
|
6
|
+
const ContextMenuTrigger = ContextMenuPrimitive.Trigger;
|
|
7
|
+
const ContextMenuGroup = ContextMenuPrimitive.Group;
|
|
8
|
+
const ContextMenuPortal = ContextMenuPrimitive.Portal;
|
|
9
|
+
const ContextMenuSub = ContextMenuPrimitive.Sub;
|
|
10
|
+
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
|
|
11
|
+
|
|
12
|
+
const ContextMenuSubTrigger = forwardRef<
|
|
13
|
+
React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
|
|
14
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
|
|
15
|
+
inset?: boolean;
|
|
16
|
+
}
|
|
17
|
+
>(({ className, inset, children, ...props }, ref) => (
|
|
18
|
+
<ContextMenuPrimitive.SubTrigger
|
|
19
|
+
ref={ref}
|
|
20
|
+
className={cn(
|
|
21
|
+
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none',
|
|
22
|
+
'focus:bg-white/10 data-[state=open]:bg-white/10',
|
|
23
|
+
inset && 'pl-8',
|
|
24
|
+
className
|
|
25
|
+
)}
|
|
26
|
+
{...props}
|
|
27
|
+
>
|
|
28
|
+
{children}
|
|
29
|
+
<span className="ml-auto text-xs">▶</span>
|
|
30
|
+
</ContextMenuPrimitive.SubTrigger>
|
|
31
|
+
));
|
|
32
|
+
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName;
|
|
33
|
+
|
|
34
|
+
const ContextMenuSubContent = forwardRef<
|
|
35
|
+
React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
|
|
36
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
|
|
37
|
+
>(({ className, ...props }, ref) => (
|
|
38
|
+
<ContextMenuPrimitive.SubContent
|
|
39
|
+
ref={ref}
|
|
40
|
+
className={cn(
|
|
41
|
+
'z-50 min-w-[8rem] overflow-hidden rounded-md border border-node-border bg-panel-bg p-1 shadow-lg',
|
|
42
|
+
'data-[state=open]:animate-in data-[state=closed]:animate-out',
|
|
43
|
+
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
|
44
|
+
'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
|
|
45
|
+
className
|
|
46
|
+
)}
|
|
47
|
+
{...props}
|
|
48
|
+
/>
|
|
49
|
+
));
|
|
50
|
+
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName;
|
|
51
|
+
|
|
52
|
+
const ContextMenuContent = forwardRef<
|
|
53
|
+
React.ElementRef<typeof ContextMenuPrimitive.Content>,
|
|
54
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
|
|
55
|
+
>(({ className, ...props }, ref) => (
|
|
56
|
+
<ContextMenuPrimitive.Portal>
|
|
57
|
+
<ContextMenuPrimitive.Content
|
|
58
|
+
ref={ref}
|
|
59
|
+
className={cn(
|
|
60
|
+
'z-50 min-w-[12rem] overflow-hidden rounded-md border border-node-border bg-panel-bg p-1 shadow-lg',
|
|
61
|
+
'animate-in fade-in-80 data-[state=open]:animate-in data-[state=closed]:animate-out',
|
|
62
|
+
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
|
63
|
+
'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
|
|
64
|
+
className
|
|
65
|
+
)}
|
|
66
|
+
{...props}
|
|
67
|
+
/>
|
|
68
|
+
</ContextMenuPrimitive.Portal>
|
|
69
|
+
));
|
|
70
|
+
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName;
|
|
71
|
+
|
|
72
|
+
const ContextMenuItem = forwardRef<
|
|
73
|
+
React.ElementRef<typeof ContextMenuPrimitive.Item>,
|
|
74
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
|
|
75
|
+
inset?: boolean;
|
|
76
|
+
destructive?: boolean;
|
|
77
|
+
}
|
|
78
|
+
>(({ className, inset, destructive, ...props }, ref) => (
|
|
79
|
+
<ContextMenuPrimitive.Item
|
|
80
|
+
ref={ref}
|
|
81
|
+
className={cn(
|
|
82
|
+
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none',
|
|
83
|
+
'focus:bg-white/10 focus:text-white',
|
|
84
|
+
'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
|
85
|
+
destructive && 'text-error focus:bg-error/10 focus:text-error',
|
|
86
|
+
inset && 'pl-8',
|
|
87
|
+
className
|
|
88
|
+
)}
|
|
89
|
+
{...props}
|
|
90
|
+
/>
|
|
91
|
+
));
|
|
92
|
+
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName;
|
|
93
|
+
|
|
94
|
+
const ContextMenuCheckboxItem = forwardRef<
|
|
95
|
+
React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
|
|
96
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
|
|
97
|
+
>(({ className, children, checked, ...props }, ref) => (
|
|
98
|
+
<ContextMenuPrimitive.CheckboxItem
|
|
99
|
+
ref={ref}
|
|
100
|
+
className={cn(
|
|
101
|
+
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none',
|
|
102
|
+
'focus:bg-white/10 focus:text-white',
|
|
103
|
+
'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
|
104
|
+
className
|
|
105
|
+
)}
|
|
106
|
+
checked={checked}
|
|
107
|
+
{...props}
|
|
108
|
+
>
|
|
109
|
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
110
|
+
<ContextMenuPrimitive.ItemIndicator>
|
|
111
|
+
<span className="text-primary">✓</span>
|
|
112
|
+
</ContextMenuPrimitive.ItemIndicator>
|
|
113
|
+
</span>
|
|
114
|
+
{children}
|
|
115
|
+
</ContextMenuPrimitive.CheckboxItem>
|
|
116
|
+
));
|
|
117
|
+
ContextMenuCheckboxItem.displayName = ContextMenuPrimitive.CheckboxItem.displayName;
|
|
118
|
+
|
|
119
|
+
const ContextMenuRadioItem = forwardRef<
|
|
120
|
+
React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
|
|
121
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
|
|
122
|
+
>(({ className, children, ...props }, ref) => (
|
|
123
|
+
<ContextMenuPrimitive.RadioItem
|
|
124
|
+
ref={ref}
|
|
125
|
+
className={cn(
|
|
126
|
+
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none',
|
|
127
|
+
'focus:bg-white/10 focus:text-white',
|
|
128
|
+
'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
|
129
|
+
className
|
|
130
|
+
)}
|
|
131
|
+
{...props}
|
|
132
|
+
>
|
|
133
|
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
134
|
+
<ContextMenuPrimitive.ItemIndicator>
|
|
135
|
+
<span className="h-2 w-2 rounded-full bg-primary" />
|
|
136
|
+
</ContextMenuPrimitive.ItemIndicator>
|
|
137
|
+
</span>
|
|
138
|
+
{children}
|
|
139
|
+
</ContextMenuPrimitive.RadioItem>
|
|
140
|
+
));
|
|
141
|
+
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName;
|
|
142
|
+
|
|
143
|
+
const ContextMenuLabel = forwardRef<
|
|
144
|
+
React.ElementRef<typeof ContextMenuPrimitive.Label>,
|
|
145
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
|
|
146
|
+
inset?: boolean;
|
|
147
|
+
}
|
|
148
|
+
>(({ className, inset, ...props }, ref) => (
|
|
149
|
+
<ContextMenuPrimitive.Label
|
|
150
|
+
ref={ref}
|
|
151
|
+
className={cn(
|
|
152
|
+
'px-2 py-1.5 text-xs font-semibold text-gray-400',
|
|
153
|
+
inset && 'pl-8',
|
|
154
|
+
className
|
|
155
|
+
)}
|
|
156
|
+
{...props}
|
|
157
|
+
/>
|
|
158
|
+
));
|
|
159
|
+
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName;
|
|
160
|
+
|
|
161
|
+
const ContextMenuSeparator = forwardRef<
|
|
162
|
+
React.ElementRef<typeof ContextMenuPrimitive.Separator>,
|
|
163
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
|
|
164
|
+
>(({ className, ...props }, ref) => (
|
|
165
|
+
<ContextMenuPrimitive.Separator
|
|
166
|
+
ref={ref}
|
|
167
|
+
className={cn('-mx-1 my-1 h-px bg-node-border', className)}
|
|
168
|
+
{...props}
|
|
169
|
+
/>
|
|
170
|
+
));
|
|
171
|
+
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName;
|
|
172
|
+
|
|
173
|
+
const ContextMenuShortcut = ({
|
|
174
|
+
className,
|
|
175
|
+
...props
|
|
176
|
+
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
|
177
|
+
return (
|
|
178
|
+
<span
|
|
179
|
+
className={cn('ml-auto text-xs tracking-widest text-gray-500', className)}
|
|
180
|
+
{...props}
|
|
181
|
+
/>
|
|
182
|
+
);
|
|
183
|
+
};
|
|
184
|
+
ContextMenuShortcut.displayName = 'ContextMenuShortcut';
|
|
185
|
+
|
|
186
|
+
export {
|
|
187
|
+
ContextMenu,
|
|
188
|
+
ContextMenuTrigger,
|
|
189
|
+
ContextMenuContent,
|
|
190
|
+
ContextMenuItem,
|
|
191
|
+
ContextMenuCheckboxItem,
|
|
192
|
+
ContextMenuRadioItem,
|
|
193
|
+
ContextMenuLabel,
|
|
194
|
+
ContextMenuSeparator,
|
|
195
|
+
ContextMenuShortcut,
|
|
196
|
+
ContextMenuGroup,
|
|
197
|
+
ContextMenuPortal,
|
|
198
|
+
ContextMenuSub,
|
|
199
|
+
ContextMenuSubContent,
|
|
200
|
+
ContextMenuSubTrigger,
|
|
201
|
+
ContextMenuRadioGroup,
|
|
202
|
+
};
|