@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,143 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { Modal, ModalFooter } from './Modal';
|
|
3
|
+
import { Button } from './Button';
|
|
4
|
+
import { Keyboard } from 'lucide-react';
|
|
5
|
+
|
|
6
|
+
interface Shortcut {
|
|
7
|
+
keys: string[];
|
|
8
|
+
description: string;
|
|
9
|
+
category: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const shortcuts: Shortcut[] = [
|
|
13
|
+
// General
|
|
14
|
+
{ keys: ['⌘', 'S'], description: 'Save workflow', category: 'General' },
|
|
15
|
+
{ keys: ['⌘', 'Z'], description: 'Undo', category: 'General' },
|
|
16
|
+
{ keys: ['⌘', '⇧', 'Z'], description: 'Redo', category: 'General' },
|
|
17
|
+
{ keys: ['⌘', '?'], description: 'Show keyboard shortcuts', category: 'General' },
|
|
18
|
+
|
|
19
|
+
// Canvas
|
|
20
|
+
{ keys: ['N'], description: 'Add new step', category: 'Canvas' },
|
|
21
|
+
{ keys: ['⌘', 'L'], description: 'Auto-layout canvas', category: 'Canvas' },
|
|
22
|
+
{ keys: ['⌘', '0'], description: 'Fit view', category: 'Canvas' },
|
|
23
|
+
{ keys: ['⌘', '+'], description: 'Zoom in', category: 'Canvas' },
|
|
24
|
+
{ keys: ['⌘', '-'], description: 'Zoom out', category: 'Canvas' },
|
|
25
|
+
{ keys: ['⌫'], description: 'Delete selected', category: 'Canvas' },
|
|
26
|
+
{ keys: ['⌘', 'D'], description: 'Duplicate selected', category: 'Canvas' },
|
|
27
|
+
|
|
28
|
+
// Editing
|
|
29
|
+
{ keys: ['Double-click'], description: 'Edit step', category: 'Editing' },
|
|
30
|
+
{ keys: ['E'], description: 'Edit selected step', category: 'Editing' },
|
|
31
|
+
{ keys: ['Y'], description: 'View YAML', category: 'Editing' },
|
|
32
|
+
{ keys: ['Escape'], description: 'Close modal / deselect', category: 'Editing' },
|
|
33
|
+
|
|
34
|
+
// Navigation
|
|
35
|
+
{ keys: ['⌘', '←'], description: 'Back to parent workflow', category: 'Navigation' },
|
|
36
|
+
{ keys: ['⌘', '↑'], description: 'Go to root workflow', category: 'Navigation' },
|
|
37
|
+
|
|
38
|
+
// Execution
|
|
39
|
+
{ keys: ['⌘', '↵'], description: 'Execute workflow', category: 'Execution' },
|
|
40
|
+
|
|
41
|
+
// Debugging
|
|
42
|
+
{ keys: ['F9'], description: 'Toggle debug mode', category: 'Debugging' },
|
|
43
|
+
{ keys: ['F5'], description: 'Continue execution', category: 'Debugging' },
|
|
44
|
+
{ keys: ['F10'], description: 'Step over', category: 'Debugging' },
|
|
45
|
+
{ keys: ['F11'], description: 'Step into', category: 'Debugging' },
|
|
46
|
+
{ keys: ['⇧', 'F11'], description: 'Step out', category: 'Debugging' },
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
interface KeyboardShortcutsProps {
|
|
50
|
+
open: boolean;
|
|
51
|
+
onOpenChange: (open: boolean) => void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function KeyboardShortcuts({ open, onOpenChange }: KeyboardShortcutsProps) {
|
|
55
|
+
// Group shortcuts by category
|
|
56
|
+
const categories = [...new Set(shortcuts.map((s) => s.category))];
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<Modal
|
|
60
|
+
open={open}
|
|
61
|
+
onOpenChange={onOpenChange}
|
|
62
|
+
title="Keyboard Shortcuts"
|
|
63
|
+
description="Quick reference for all keyboard shortcuts"
|
|
64
|
+
size="md"
|
|
65
|
+
>
|
|
66
|
+
<div className="p-4 space-y-6 max-h-[60vh] overflow-y-auto">
|
|
67
|
+
{categories.map((category) => (
|
|
68
|
+
<div key={category}>
|
|
69
|
+
<h3 className="text-sm font-medium text-gray-400 uppercase tracking-wider mb-3">
|
|
70
|
+
{category}
|
|
71
|
+
</h3>
|
|
72
|
+
<div className="space-y-2">
|
|
73
|
+
{shortcuts
|
|
74
|
+
.filter((s) => s.category === category)
|
|
75
|
+
.map((shortcut, index) => (
|
|
76
|
+
<div
|
|
77
|
+
key={index}
|
|
78
|
+
className="flex items-center justify-between py-1.5 border-b border-node-border last:border-0"
|
|
79
|
+
>
|
|
80
|
+
<span className="text-sm text-gray-300">{shortcut.description}</span>
|
|
81
|
+
<div className="flex items-center gap-1">
|
|
82
|
+
{shortcut.keys.map((key, i) => (
|
|
83
|
+
<kbd
|
|
84
|
+
key={i}
|
|
85
|
+
className="px-2 py-1 bg-node-bg border border-node-border rounded text-xs font-mono text-gray-300"
|
|
86
|
+
>
|
|
87
|
+
{key}
|
|
88
|
+
</kbd>
|
|
89
|
+
))}
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
))}
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
))}
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<ModalFooter>
|
|
99
|
+
<Button variant="secondary" onClick={() => onOpenChange(false)}>
|
|
100
|
+
Close
|
|
101
|
+
</Button>
|
|
102
|
+
</ModalFooter>
|
|
103
|
+
</Modal>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Hook to manage keyboard shortcuts modal
|
|
108
|
+
export function useKeyboardShortcuts() {
|
|
109
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
110
|
+
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
113
|
+
// ⌘+? or Ctrl+? to open shortcuts
|
|
114
|
+
if ((e.metaKey || e.ctrlKey) && e.key === '/') {
|
|
115
|
+
e.preventDefault();
|
|
116
|
+
setIsOpen(true);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
121
|
+
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
122
|
+
}, []);
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
isOpen,
|
|
126
|
+
setIsOpen,
|
|
127
|
+
openShortcuts: () => setIsOpen(true),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Small button to show keyboard shortcuts
|
|
132
|
+
export function KeyboardShortcutsButton({ onClick }: { onClick: () => void }) {
|
|
133
|
+
return (
|
|
134
|
+
<button
|
|
135
|
+
onClick={onClick}
|
|
136
|
+
className="flex items-center gap-1.5 px-2 py-1 text-xs text-gray-500 hover:text-gray-300 transition-colors"
|
|
137
|
+
title="Keyboard shortcuts (⌘/)"
|
|
138
|
+
>
|
|
139
|
+
<Keyboard className="w-3.5 h-3.5" />
|
|
140
|
+
<span className="hidden sm:inline">Shortcuts</span>
|
|
141
|
+
</button>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import * as Dialog from '@radix-ui/react-dialog';
|
|
2
|
+
import { X } from 'lucide-react';
|
|
3
|
+
import { cn } from '../../utils/cn';
|
|
4
|
+
|
|
5
|
+
interface ModalProps {
|
|
6
|
+
open: boolean;
|
|
7
|
+
onOpenChange: (open: boolean) => void;
|
|
8
|
+
title?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
children: React.ReactNode;
|
|
11
|
+
className?: string;
|
|
12
|
+
size?: 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const sizeClasses = {
|
|
16
|
+
sm: 'max-w-md',
|
|
17
|
+
md: 'max-w-lg',
|
|
18
|
+
lg: 'max-w-2xl',
|
|
19
|
+
xl: 'max-w-4xl',
|
|
20
|
+
full: 'max-w-[90vw]',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function Modal({
|
|
24
|
+
open,
|
|
25
|
+
onOpenChange,
|
|
26
|
+
title,
|
|
27
|
+
description,
|
|
28
|
+
children,
|
|
29
|
+
className,
|
|
30
|
+
size = 'md',
|
|
31
|
+
}: ModalProps) {
|
|
32
|
+
return (
|
|
33
|
+
<Dialog.Root open={open} onOpenChange={onOpenChange}>
|
|
34
|
+
<Dialog.Portal>
|
|
35
|
+
<Dialog.Overlay className="fixed inset-0 z-50 bg-black/50 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0" />
|
|
36
|
+
<Dialog.Content
|
|
37
|
+
className={cn(
|
|
38
|
+
'fixed left-[50%] top-[50%] z-50 translate-x-[-50%] translate-y-[-50%]',
|
|
39
|
+
'w-full rounded-lg border border-node-border bg-panel-bg shadow-lg',
|
|
40
|
+
'data-[state=open]:animate-in data-[state=closed]:animate-out',
|
|
41
|
+
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
|
42
|
+
'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
|
|
43
|
+
'data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%]',
|
|
44
|
+
'data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]',
|
|
45
|
+
'duration-200',
|
|
46
|
+
sizeClasses[size],
|
|
47
|
+
className
|
|
48
|
+
)}
|
|
49
|
+
>
|
|
50
|
+
{(title || description) && (
|
|
51
|
+
<div className="flex items-start justify-between p-4 border-b border-node-border">
|
|
52
|
+
<div>
|
|
53
|
+
{title && (
|
|
54
|
+
<Dialog.Title className="text-lg font-semibold text-white">
|
|
55
|
+
{title}
|
|
56
|
+
</Dialog.Title>
|
|
57
|
+
)}
|
|
58
|
+
{description && (
|
|
59
|
+
<Dialog.Description className="mt-1 text-sm text-gray-400">
|
|
60
|
+
{description}
|
|
61
|
+
</Dialog.Description>
|
|
62
|
+
)}
|
|
63
|
+
</div>
|
|
64
|
+
<Dialog.Close className="rounded-full p-1 hover:bg-white/10 transition-colors">
|
|
65
|
+
<X className="w-5 h-5 text-gray-400" />
|
|
66
|
+
</Dialog.Close>
|
|
67
|
+
</div>
|
|
68
|
+
)}
|
|
69
|
+
<div className="max-h-[calc(85vh-8rem)] overflow-y-auto">{children}</div>
|
|
70
|
+
</Dialog.Content>
|
|
71
|
+
</Dialog.Portal>
|
|
72
|
+
</Dialog.Root>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function ModalFooter({
|
|
77
|
+
children,
|
|
78
|
+
className,
|
|
79
|
+
}: {
|
|
80
|
+
children: React.ReactNode;
|
|
81
|
+
className?: string;
|
|
82
|
+
}) {
|
|
83
|
+
return (
|
|
84
|
+
<div
|
|
85
|
+
className={cn(
|
|
86
|
+
'flex items-center justify-end gap-3 p-4 border-t border-node-border',
|
|
87
|
+
className
|
|
88
|
+
)}
|
|
89
|
+
>
|
|
90
|
+
{children}
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as TabsPrimitive from '@radix-ui/react-tabs';
|
|
2
|
+
import { forwardRef } from 'react';
|
|
3
|
+
import { cn } from '../../utils/cn';
|
|
4
|
+
|
|
5
|
+
const Tabs = TabsPrimitive.Root;
|
|
6
|
+
|
|
7
|
+
const TabsList = forwardRef<
|
|
8
|
+
React.ElementRef<typeof TabsPrimitive.List>,
|
|
9
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
|
10
|
+
>(({ className, ...props }, ref) => (
|
|
11
|
+
<TabsPrimitive.List
|
|
12
|
+
ref={ref}
|
|
13
|
+
className={cn(
|
|
14
|
+
'inline-flex items-center gap-1 border-b border-node-border w-full px-2',
|
|
15
|
+
className
|
|
16
|
+
)}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
));
|
|
20
|
+
TabsList.displayName = TabsPrimitive.List.displayName;
|
|
21
|
+
|
|
22
|
+
const TabsTrigger = forwardRef<
|
|
23
|
+
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
|
24
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
|
25
|
+
>(({ className, ...props }, ref) => (
|
|
26
|
+
<TabsPrimitive.Trigger
|
|
27
|
+
ref={ref}
|
|
28
|
+
className={cn(
|
|
29
|
+
'inline-flex items-center justify-center whitespace-nowrap px-3 py-2 text-sm font-medium',
|
|
30
|
+
'text-gray-400 transition-all',
|
|
31
|
+
'hover:text-gray-200',
|
|
32
|
+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50',
|
|
33
|
+
'disabled:pointer-events-none disabled:opacity-50',
|
|
34
|
+
'data-[state=active]:text-primary data-[state=active]:border-b-2 data-[state=active]:border-primary data-[state=active]:-mb-px',
|
|
35
|
+
className
|
|
36
|
+
)}
|
|
37
|
+
{...props}
|
|
38
|
+
/>
|
|
39
|
+
));
|
|
40
|
+
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
|
|
41
|
+
|
|
42
|
+
const TabsContent = forwardRef<
|
|
43
|
+
React.ElementRef<typeof TabsPrimitive.Content>,
|
|
44
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
|
45
|
+
>(({ className, ...props }, ref) => (
|
|
46
|
+
<TabsPrimitive.Content
|
|
47
|
+
ref={ref}
|
|
48
|
+
className={cn(
|
|
49
|
+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50',
|
|
50
|
+
className
|
|
51
|
+
)}
|
|
52
|
+
{...props}
|
|
53
|
+
/>
|
|
54
|
+
));
|
|
55
|
+
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
|
56
|
+
|
|
57
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Sun, Moon, Monitor } from 'lucide-react';
|
|
2
|
+
import { useThemeStore, type Theme } from '../../stores/themeStore';
|
|
3
|
+
|
|
4
|
+
interface ThemeToggleProps {
|
|
5
|
+
showLabel?: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function ThemeToggle({ showLabel = false }: ThemeToggleProps) {
|
|
9
|
+
const { theme, toggleTheme } = useThemeStore();
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<button
|
|
13
|
+
onClick={toggleTheme}
|
|
14
|
+
className="flex items-center gap-2 px-2 py-1.5 rounded-lg hover:bg-white/10 dark:hover:bg-white/10 light:hover:bg-black/5 transition-colors"
|
|
15
|
+
title={`Switch to ${theme === 'dark' ? 'light' : 'dark'} mode`}
|
|
16
|
+
>
|
|
17
|
+
{theme === 'dark' ? (
|
|
18
|
+
<Moon className="w-4 h-4 text-gray-400" />
|
|
19
|
+
) : (
|
|
20
|
+
<Sun className="w-4 h-4 text-yellow-500" />
|
|
21
|
+
)}
|
|
22
|
+
{showLabel && (
|
|
23
|
+
<span className="text-xs text-gray-400">
|
|
24
|
+
{theme === 'dark' ? 'Dark' : 'Light'}
|
|
25
|
+
</span>
|
|
26
|
+
)}
|
|
27
|
+
</button>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface ThemePickerProps {
|
|
32
|
+
className?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function ThemePicker({ className }: ThemePickerProps) {
|
|
36
|
+
const { theme, setTheme } = useThemeStore();
|
|
37
|
+
|
|
38
|
+
const themes: { value: Theme; icon: typeof Sun; label: string }[] = [
|
|
39
|
+
{ value: 'light', icon: Sun, label: 'Light' },
|
|
40
|
+
{ value: 'dark', icon: Moon, label: 'Dark' },
|
|
41
|
+
{ value: 'system', icon: Monitor, label: 'System' },
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div className={`flex items-center gap-1 ${className}`}>
|
|
46
|
+
{themes.map(({ value, icon: Icon, label }) => (
|
|
47
|
+
<button
|
|
48
|
+
key={value}
|
|
49
|
+
onClick={() => setTheme(value)}
|
|
50
|
+
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs transition-colors ${
|
|
51
|
+
theme === value
|
|
52
|
+
? 'bg-primary/20 text-primary'
|
|
53
|
+
: 'text-gray-400 hover:text-white hover:bg-white/10'
|
|
54
|
+
}`}
|
|
55
|
+
title={`${label} mode`}
|
|
56
|
+
>
|
|
57
|
+
<Icon className="w-4 h-4" />
|
|
58
|
+
<span className="hidden sm:inline">{label}</span>
|
|
59
|
+
</button>
|
|
60
|
+
))}
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Canvas components
|
|
2
|
+
export { Canvas } from './Canvas/Canvas';
|
|
3
|
+
export { StepNode } from './Canvas/StepNode';
|
|
4
|
+
export { SubWorkflowNode } from './Canvas/SubWorkflowNode';
|
|
5
|
+
export { TriggerNode } from './Canvas/TriggerNode';
|
|
6
|
+
export { OutputNode } from './Canvas/OutputNode';
|
|
7
|
+
export { Toolbar } from './Canvas/Toolbar';
|
|
8
|
+
export { ExecutionOverlay } from './Canvas/ExecutionOverlay';
|
|
9
|
+
export { NodeContextMenu, CanvasContextMenu } from './Canvas/NodeContextMenu';
|
|
10
|
+
|
|
11
|
+
// Editor components
|
|
12
|
+
export { StepEditor } from './Editor/StepEditor';
|
|
13
|
+
export { YamlEditor, YamlViewer } from './Editor/YamlEditor';
|
|
14
|
+
export { InputsEditor } from './Editor/InputsEditor';
|
|
15
|
+
export { NewStepWizard } from './Editor/NewStepWizard';
|
|
16
|
+
|
|
17
|
+
// Common components
|
|
18
|
+
export { Button } from './common/Button';
|
|
19
|
+
export { Modal, ModalFooter } from './common/Modal';
|
|
20
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent } from './common/Tabs';
|
|
21
|
+
export { ThemeToggle, ThemePicker } from './common/ThemeToggle';
|
|
22
|
+
export * from './common/ContextMenu';
|
|
23
|
+
|
|
24
|
+
// Prompt components
|
|
25
|
+
export { PromptInput } from './Prompt/PromptInput';
|
|
26
|
+
export { ChangePreview } from './Prompt/ChangePreview';
|
|
27
|
+
|
|
28
|
+
// Panel components
|
|
29
|
+
export { PropertiesPanel } from './Panels/PropertiesPanel';
|
|
30
|
+
|
|
31
|
+
// Sidebar components
|
|
32
|
+
export { Sidebar } from './Sidebar/Sidebar';
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { usePromptStore } from '../stores/promptStore';
|
|
3
|
+
import { useWorkflowStore } from '../stores/workflowStore';
|
|
4
|
+
|
|
5
|
+
export function useAIPrompt() {
|
|
6
|
+
const {
|
|
7
|
+
isProcessing,
|
|
8
|
+
history,
|
|
9
|
+
lastError,
|
|
10
|
+
pendingChanges,
|
|
11
|
+
sendPrompt,
|
|
12
|
+
acceptChanges,
|
|
13
|
+
rejectChanges,
|
|
14
|
+
clearHistory,
|
|
15
|
+
} = usePromptStore();
|
|
16
|
+
|
|
17
|
+
const currentWorkflow = useWorkflowStore((s) => s.currentWorkflow);
|
|
18
|
+
|
|
19
|
+
// Send a prompt with the current workflow context
|
|
20
|
+
const submitPrompt = useCallback(
|
|
21
|
+
async (prompt: string) => {
|
|
22
|
+
if (!prompt.trim()) return;
|
|
23
|
+
await sendPrompt(prompt);
|
|
24
|
+
},
|
|
25
|
+
[sendPrompt]
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
// Quick action helpers
|
|
29
|
+
const addStep = useCallback(
|
|
30
|
+
async (service: string, description?: string) => {
|
|
31
|
+
const prompt = description
|
|
32
|
+
? `Add a ${service} step: ${description}`
|
|
33
|
+
: `Add a ${service} step at the end of the workflow`;
|
|
34
|
+
await submitPrompt(prompt);
|
|
35
|
+
},
|
|
36
|
+
[submitPrompt]
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const modifyStep = useCallback(
|
|
40
|
+
async (stepId: string, modification: string) => {
|
|
41
|
+
const prompt = `Modify step "${stepId}": ${modification}`;
|
|
42
|
+
await submitPrompt(prompt);
|
|
43
|
+
},
|
|
44
|
+
[submitPrompt]
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const addErrorHandling = useCallback(
|
|
48
|
+
async (stepId?: string, retries = 3) => {
|
|
49
|
+
const prompt = stepId
|
|
50
|
+
? `Add error handling with ${retries} retries to step "${stepId}"`
|
|
51
|
+
: `Add error handling with ${retries} retries to all steps`;
|
|
52
|
+
await submitPrompt(prompt);
|
|
53
|
+
},
|
|
54
|
+
[submitPrompt]
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const addCondition = useCallback(
|
|
58
|
+
async (stepId: string, condition: string) => {
|
|
59
|
+
const prompt = `Add a condition to step "${stepId}": only run if ${condition}`;
|
|
60
|
+
await submitPrompt(prompt);
|
|
61
|
+
},
|
|
62
|
+
[submitPrompt]
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const convertToSubworkflow = useCallback(
|
|
66
|
+
async (stepIds: string[], name: string) => {
|
|
67
|
+
const prompt = `Convert steps ${stepIds.join(', ')} into a sub-workflow called "${name}"`;
|
|
68
|
+
await submitPrompt(prompt);
|
|
69
|
+
},
|
|
70
|
+
[submitPrompt]
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const explainWorkflow = useCallback(async () => {
|
|
74
|
+
const prompt =
|
|
75
|
+
'Explain what this workflow does, step by step, in simple terms';
|
|
76
|
+
await submitPrompt(prompt);
|
|
77
|
+
}, [submitPrompt]);
|
|
78
|
+
|
|
79
|
+
const suggestImprovements = useCallback(async () => {
|
|
80
|
+
const prompt =
|
|
81
|
+
'Analyze this workflow and suggest improvements for error handling, efficiency, or reliability';
|
|
82
|
+
await submitPrompt(prompt);
|
|
83
|
+
}, [submitPrompt]);
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
// State
|
|
87
|
+
isProcessing,
|
|
88
|
+
history,
|
|
89
|
+
lastError,
|
|
90
|
+
pendingChanges,
|
|
91
|
+
currentWorkflow,
|
|
92
|
+
|
|
93
|
+
// Core actions
|
|
94
|
+
submitPrompt,
|
|
95
|
+
acceptChanges,
|
|
96
|
+
rejectChanges,
|
|
97
|
+
clearHistory,
|
|
98
|
+
|
|
99
|
+
// Quick actions
|
|
100
|
+
addStep,
|
|
101
|
+
modifyStep,
|
|
102
|
+
addErrorHandling,
|
|
103
|
+
addCondition,
|
|
104
|
+
convertToSubworkflow,
|
|
105
|
+
explainWorkflow,
|
|
106
|
+
suggestImprovements,
|
|
107
|
+
};
|
|
108
|
+
}
|