@marktoflow/gui 2.0.0-alpha.5 → 2.0.2

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.
Files changed (162) hide show
  1. package/README.md +48 -180
  2. package/dist/client/assets/index-DQeR1ew6.css +1 -0
  3. package/dist/client/assets/index-LbIVPHbD.js +833 -0
  4. package/dist/client/assets/index-LbIVPHbD.js.map +1 -0
  5. package/dist/client/index.html +2 -2
  6. package/dist/client/marktoflow-logo.png +0 -0
  7. package/dist/server/index.js +31 -5
  8. package/dist/server/index.js.map +1 -1
  9. package/dist/server/routes/admin.js +95 -0
  10. package/dist/server/routes/admin.js.map +1 -0
  11. package/dist/server/routes/ai.js +2 -2
  12. package/dist/server/routes/ai.js.map +1 -1
  13. package/dist/server/routes/collaboration.js +104 -0
  14. package/dist/server/routes/collaboration.js.map +1 -0
  15. package/dist/server/routes/execute.js +181 -14
  16. package/dist/server/routes/execute.js.map +1 -1
  17. package/dist/server/routes/form.js +160 -0
  18. package/dist/server/routes/form.js.map +1 -0
  19. package/dist/server/routes/settings.js +90 -0
  20. package/dist/server/routes/settings.js.map +1 -0
  21. package/dist/server/routes/templates.js +106 -0
  22. package/dist/server/routes/templates.js.map +1 -0
  23. package/dist/server/routes/versions.js +101 -0
  24. package/dist/server/routes/versions.js.map +1 -0
  25. package/dist/server/services/AIService.js +85 -2
  26. package/dist/server/services/AIService.js.map +1 -1
  27. package/dist/server/services/ExecutionManager.js +571 -0
  28. package/dist/server/services/ExecutionManager.js.map +1 -0
  29. package/dist/server/services/VersionService.js +65 -0
  30. package/dist/server/services/VersionService.js.map +1 -0
  31. package/dist/server/services/WorkflowService.js +8 -2
  32. package/dist/server/services/WorkflowService.js.map +1 -1
  33. package/dist/server/services/agents/copilot-provider.js +32 -0
  34. package/dist/server/services/agents/copilot-provider.js.map +1 -1
  35. package/dist/server/websocket/index.js +42 -0
  36. package/dist/server/websocket/index.js.map +1 -1
  37. package/dist/shared/constants.js +9 -0
  38. package/dist/shared/constants.js.map +1 -1
  39. package/dist/shared/settings.js +51 -0
  40. package/dist/shared/settings.js.map +1 -0
  41. package/package.json +14 -10
  42. package/public/marktoflow-logo.png +0 -0
  43. package/tests/integration/fixtures/test-workflow.md +6 -0
  44. package/.turbo/turbo-build.log +0 -42
  45. package/dist/client/assets/index-CM44OayM.js +0 -704
  46. package/dist/client/assets/index-CM44OayM.js.map +0 -1
  47. package/dist/client/assets/index-Dru63gi6.css +0 -1
  48. package/marktoflow-gui-2.0.0-alpha.5.tgz +0 -0
  49. package/playwright.config.ts +0 -27
  50. package/postcss.config.js +0 -6
  51. package/src/client/App.tsx +0 -520
  52. package/src/client/components/Canvas/Canvas.tsx +0 -425
  53. package/src/client/components/Canvas/ExecutionOverlay.tsx +0 -935
  54. package/src/client/components/Canvas/ForEachNode.tsx +0 -152
  55. package/src/client/components/Canvas/IfElseNode.tsx +0 -141
  56. package/src/client/components/Canvas/NodeContextMenu.tsx +0 -192
  57. package/src/client/components/Canvas/OutputNode.tsx +0 -111
  58. package/src/client/components/Canvas/ParallelNode.tsx +0 -157
  59. package/src/client/components/Canvas/StepNode.tsx +0 -106
  60. package/src/client/components/Canvas/SubWorkflowNode.tsx +0 -141
  61. package/src/client/components/Canvas/SwitchNode.tsx +0 -185
  62. package/src/client/components/Canvas/Toolbar.tsx +0 -227
  63. package/src/client/components/Canvas/TransformNode.tsx +0 -194
  64. package/src/client/components/Canvas/TriggerNode.tsx +0 -128
  65. package/src/client/components/Canvas/TryCatchNode.tsx +0 -164
  66. package/src/client/components/Canvas/WhileNode.tsx +0 -161
  67. package/src/client/components/Canvas/index.ts +0 -24
  68. package/src/client/components/Debug/VariableInspector.tsx +0 -148
  69. package/src/client/components/Editor/InputsEditor.tsx +0 -458
  70. package/src/client/components/Editor/NewStepWizard.tsx +0 -344
  71. package/src/client/components/Editor/StepEditor.tsx +0 -532
  72. package/src/client/components/Editor/YamlEditor.tsx +0 -160
  73. package/src/client/components/Panels/PropertiesPanel.tsx +0 -589
  74. package/src/client/components/Prompt/ChangePreview.tsx +0 -281
  75. package/src/client/components/Prompt/PromptHistoryPanel.tsx +0 -209
  76. package/src/client/components/Prompt/PromptInput.tsx +0 -110
  77. package/src/client/components/Settings/ProviderSwitcher.tsx +0 -228
  78. package/src/client/components/Sidebar/ImportDialog.tsx +0 -257
  79. package/src/client/components/Sidebar/Sidebar.tsx +0 -362
  80. package/src/client/components/common/Breadcrumb.tsx +0 -40
  81. package/src/client/components/common/Button.tsx +0 -68
  82. package/src/client/components/common/ContextMenu.tsx +0 -202
  83. package/src/client/components/common/KeyboardShortcuts.tsx +0 -149
  84. package/src/client/components/common/Modal.tsx +0 -93
  85. package/src/client/components/common/Tabs.tsx +0 -57
  86. package/src/client/components/common/ThemeToggle.tsx +0 -63
  87. package/src/client/components/index.ts +0 -32
  88. package/src/client/hooks/index.ts +0 -4
  89. package/src/client/hooks/useAIPrompt.ts +0 -108
  90. package/src/client/hooks/useCanvas.ts +0 -247
  91. package/src/client/hooks/useWebSocket.ts +0 -164
  92. package/src/client/hooks/useWorkflow.ts +0 -138
  93. package/src/client/main.tsx +0 -10
  94. package/src/client/stores/agentStore.ts +0 -109
  95. package/src/client/stores/canvasStore.ts +0 -348
  96. package/src/client/stores/editorStore.ts +0 -133
  97. package/src/client/stores/executionStore.ts +0 -502
  98. package/src/client/stores/index.ts +0 -4
  99. package/src/client/stores/layoutStore.ts +0 -103
  100. package/src/client/stores/navigationStore.ts +0 -49
  101. package/src/client/stores/promptStore.ts +0 -113
  102. package/src/client/stores/themeStore.ts +0 -75
  103. package/src/client/stores/workflowStore.ts +0 -185
  104. package/src/client/styles/globals.css +0 -452
  105. package/src/client/utils/cn.ts +0 -9
  106. package/src/client/utils/index.ts +0 -4
  107. package/src/client/utils/platform.ts +0 -46
  108. package/src/client/utils/serviceIcons.tsx +0 -97
  109. package/src/client/utils/stepValidation.ts +0 -155
  110. package/src/client/utils/workflowToGraph.ts +0 -523
  111. package/src/server/index.ts +0 -137
  112. package/src/server/routes/ai.ts +0 -91
  113. package/src/server/routes/execute.ts +0 -71
  114. package/src/server/routes/executions.ts +0 -136
  115. package/src/server/routes/tools.ts +0 -970
  116. package/src/server/routes/workflows.ts +0 -147
  117. package/src/server/services/AIService.ts +0 -105
  118. package/src/server/services/FileWatcher.ts +0 -69
  119. package/src/server/services/WorkflowService.ts +0 -601
  120. package/src/server/services/agents/claude-code-provider.ts +0 -320
  121. package/src/server/services/agents/claude-provider.ts +0 -248
  122. package/src/server/services/agents/codex-provider.ts +0 -398
  123. package/src/server/services/agents/copilot-provider.ts +0 -311
  124. package/src/server/services/agents/demo-provider.ts +0 -184
  125. package/src/server/services/agents/index.ts +0 -31
  126. package/src/server/services/agents/ollama-provider.ts +0 -267
  127. package/src/server/services/agents/prompts.ts +0 -509
  128. package/src/server/services/agents/registry.ts +0 -310
  129. package/src/server/services/agents/types.ts +0 -146
  130. package/src/server/websocket/index.ts +0 -117
  131. package/src/shared/constants.ts +0 -180
  132. package/src/shared/types.ts +0 -179
  133. package/tailwind.config.ts +0 -73
  134. package/tests/e2e/app.spec.ts +0 -90
  135. package/tests/e2e/canvas.spec.ts +0 -128
  136. package/tests/e2e/workflow.spec.ts +0 -185
  137. package/tests/integration/api.test.ts +0 -452
  138. package/tests/integration/testApp.ts +0 -31
  139. package/tests/setup.ts +0 -72
  140. package/tests/unit/ForEachNode.test.tsx +0 -308
  141. package/tests/unit/IfElseNode.test.tsx +0 -235
  142. package/tests/unit/ParallelNode.test.tsx +0 -344
  143. package/tests/unit/SwitchNode.test.tsx +0 -327
  144. package/tests/unit/TransformNode.test.tsx +0 -386
  145. package/tests/unit/TryCatchNode.test.tsx +0 -243
  146. package/tests/unit/WhileNode.test.tsx +0 -230
  147. package/tests/unit/agentStore.test.ts +0 -218
  148. package/tests/unit/canvasStore.test.ts +0 -502
  149. package/tests/unit/codexProvider.test.ts +0 -399
  150. package/tests/unit/components.test.tsx +0 -151
  151. package/tests/unit/executionStore.test.ts +0 -567
  152. package/tests/unit/layoutStore.test.ts +0 -194
  153. package/tests/unit/navigationStore.test.ts +0 -152
  154. package/tests/unit/platform.test.ts +0 -118
  155. package/tests/unit/serviceIcons.test.ts +0 -197
  156. package/tests/unit/stepValidation.test.ts +0 -226
  157. package/tests/unit/themeStore.test.ts +0 -141
  158. package/tests/unit/workflowToGraph.test.ts +0 -311
  159. package/tsconfig.json +0 -29
  160. package/tsconfig.server.json +0 -28
  161. package/vite.config.ts +0 -31
  162. package/vitest.config.ts +0 -26
@@ -1,228 +0,0 @@
1
- /**
2
- * Provider Switcher Component
3
- * Allows users to switch between AI providers and configure them
4
- */
5
-
6
- import { useEffect, useState } from 'react';
7
- import { useAgentStore } from '../../stores/agentStore';
8
- import { Modal, ModalFooter } from '../common/Modal';
9
- import { Button } from '../common/Button';
10
- import { Check, Settings, AlertCircle, Loader2 } from 'lucide-react';
11
-
12
- interface ProviderSwitcherProps {
13
- open: boolean;
14
- onOpenChange: (open: boolean) => void;
15
- }
16
-
17
- export function ProviderSwitcher({ open, onOpenChange }: ProviderSwitcherProps) {
18
- const { providers, activeProviderId, isLoading, error, loadProviders, setProvider } = useAgentStore();
19
- const [selectedProviderId, setSelectedProviderId] = useState<string | null>(null);
20
- const [showConfig, setShowConfig] = useState(false);
21
- const [configData, setConfigData] = useState({
22
- apiKey: '',
23
- baseUrl: '',
24
- model: '',
25
- });
26
-
27
- useEffect(() => {
28
- if (open) {
29
- loadProviders();
30
- }
31
- }, [open, loadProviders]);
32
-
33
- const handleProviderClick = async (providerId: string) => {
34
- const provider = providers.find((p) => p.id === providerId);
35
- if (!provider) return;
36
-
37
- if (provider.status === 'needs_config') {
38
- // Show config modal
39
- setSelectedProviderId(providerId);
40
- setShowConfig(true);
41
- } else if (provider.status === 'ready') {
42
- // Switch provider directly
43
- const success = await setProvider(providerId);
44
- if (success) {
45
- onOpenChange(false);
46
- }
47
- }
48
- };
49
-
50
- const handleConfigSave = async () => {
51
- if (!selectedProviderId) return;
52
-
53
- const success = await setProvider(selectedProviderId, configData);
54
- if (success) {
55
- setShowConfig(false);
56
- setConfigData({ apiKey: '', baseUrl: '', model: '' });
57
- onOpenChange(false);
58
- }
59
- };
60
-
61
- const getStatusIcon = (status: string) => {
62
- switch (status) {
63
- case 'ready':
64
- return <div className="w-2 h-2 rounded-full bg-green-500" />;
65
- case 'needs_config':
66
- return <div className="w-2 h-2 rounded-full bg-yellow-500" />;
67
- case 'unavailable':
68
- return <div className="w-2 h-2 rounded-full bg-red-500" />;
69
- default:
70
- return null;
71
- }
72
- };
73
-
74
- const getStatusLabel = (status: string) => {
75
- switch (status) {
76
- case 'ready':
77
- return 'Ready';
78
- case 'needs_config':
79
- return 'Needs Configuration';
80
- case 'unavailable':
81
- return 'Unavailable';
82
- default:
83
- return 'Unknown';
84
- }
85
- };
86
-
87
- if (showConfig) {
88
- const provider = providers.find((p) => p.id === selectedProviderId);
89
- if (!provider) return null;
90
-
91
- return (
92
- <Modal
93
- open={showConfig}
94
- onOpenChange={setShowConfig}
95
- title={`Configure ${provider.name}`}
96
- description="Enter configuration details for this provider"
97
- size="md"
98
- >
99
- <div className="p-4 space-y-4">
100
- {provider.configOptions?.apiKey && (
101
- <div>
102
- <label className="block text-sm font-medium text-gray-300 mb-2">
103
- API Key
104
- </label>
105
- <input
106
- type="password"
107
- value={configData.apiKey}
108
- onChange={(e) => setConfigData({ ...configData, apiKey: e.target.value })}
109
- className="w-full px-3 py-2 bg-node-bg border border-node-border rounded text-gray-300 focus:outline-none focus:ring-2 focus:ring-primary"
110
- placeholder="Enter API key"
111
- />
112
- </div>
113
- )}
114
-
115
- {provider.configOptions?.baseUrl && (
116
- <div>
117
- <label className="block text-sm font-medium text-gray-300 mb-2">
118
- Base URL
119
- </label>
120
- <input
121
- type="text"
122
- value={configData.baseUrl}
123
- onChange={(e) => setConfigData({ ...configData, baseUrl: e.target.value })}
124
- className="w-full px-3 py-2 bg-node-bg border border-node-border rounded text-gray-300 focus:outline-none focus:ring-2 focus:ring-primary"
125
- placeholder="Enter base URL"
126
- />
127
- </div>
128
- )}
129
-
130
- {provider.configOptions?.model && (
131
- <div>
132
- <label className="block text-sm font-medium text-gray-300 mb-2">
133
- Model
134
- </label>
135
- <input
136
- type="text"
137
- value={configData.model}
138
- onChange={(e) => setConfigData({ ...configData, model: e.target.value })}
139
- className="w-full px-3 py-2 bg-node-bg border border-node-border rounded text-gray-300 focus:outline-none focus:ring-2 focus:ring-primary"
140
- placeholder="Enter model name"
141
- />
142
- </div>
143
- )}
144
- </div>
145
-
146
- <ModalFooter>
147
- <Button variant="secondary" onClick={() => setShowConfig(false)}>
148
- Cancel
149
- </Button>
150
- <Button variant="primary" onClick={handleConfigSave} disabled={isLoading}>
151
- {isLoading ? <Loader2 className="w-4 h-4 animate-spin" /> : 'Save & Activate'}
152
- </Button>
153
- </ModalFooter>
154
- </Modal>
155
- );
156
- }
157
-
158
- return (
159
- <Modal
160
- open={open}
161
- onOpenChange={onOpenChange}
162
- title="AI Provider"
163
- description="Select which AI provider to use for agent prompts"
164
- size="md"
165
- >
166
- <div className="p-4 space-y-2">
167
- {error && (
168
- <div className="flex items-center gap-2 p-3 bg-error/10 border border-error/30 rounded text-error text-sm">
169
- <AlertCircle className="w-4 h-4 flex-shrink-0" />
170
- <span>{error}</span>
171
- </div>
172
- )}
173
-
174
- {isLoading && providers.length === 0 ? (
175
- <div className="flex items-center justify-center py-8">
176
- <Loader2 className="w-6 h-6 animate-spin text-gray-500" />
177
- </div>
178
- ) : (
179
- <div className="space-y-2">
180
- {providers.map((provider) => (
181
- <button
182
- key={provider.id}
183
- onClick={() => handleProviderClick(provider.id)}
184
- disabled={provider.status === 'unavailable'}
185
- className={`
186
- w-full flex items-center justify-between p-3 rounded border transition-all
187
- ${provider.isActive
188
- ? 'bg-primary/10 border-primary text-white'
189
- : 'bg-node-bg border-node-border text-gray-300 hover:bg-white/5'
190
- }
191
- ${provider.status === 'unavailable' ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}
192
- `}
193
- >
194
- <div className="flex items-center gap-3">
195
- {getStatusIcon(provider.status)}
196
- <div className="text-left">
197
- <div className="font-medium">{provider.name}</div>
198
- {provider.description && (
199
- <div className="text-xs text-gray-500 mt-0.5">{provider.description}</div>
200
- )}
201
- </div>
202
- </div>
203
-
204
- <div className="flex items-center gap-2">
205
- {provider.isActive && (
206
- <Check className="w-4 h-4 text-primary" />
207
- )}
208
- {provider.status === 'needs_config' && (
209
- <Settings className="w-4 h-4 text-yellow-500" />
210
- )}
211
- <span className="text-xs text-gray-500">
212
- {getStatusLabel(provider.status)}
213
- </span>
214
- </div>
215
- </button>
216
- ))}
217
- </div>
218
- )}
219
- </div>
220
-
221
- <ModalFooter>
222
- <Button variant="secondary" onClick={() => onOpenChange(false)}>
223
- Close
224
- </Button>
225
- </ModalFooter>
226
- </Modal>
227
- );
228
- }
@@ -1,257 +0,0 @@
1
- /**
2
- * Import Dialog Component
3
- * Allows users to import workflow files (.md, .yaml, .yml, .zip)
4
- */
5
-
6
- import { useState, useRef } from 'react';
7
- import { Modal, ModalFooter } from '../common/Modal';
8
- import { Button } from '../common/Button';
9
- import { Upload, FileText, Archive, CheckCircle, XCircle, AlertCircle } from 'lucide-react';
10
-
11
- interface ImportDialogProps {
12
- open: boolean;
13
- onOpenChange: (open: boolean) => void;
14
- onImportComplete?: () => void;
15
- }
16
-
17
- interface ImportResult {
18
- success: boolean;
19
- filename: string;
20
- message?: string;
21
- }
22
-
23
- export function ImportDialog({ open, onOpenChange, onImportComplete }: ImportDialogProps) {
24
- const [selectedFile, setSelectedFile] = useState<File | null>(null);
25
- const [isUploading, setIsUploading] = useState(false);
26
- const [results, setResults] = useState<ImportResult[]>([]);
27
- const [dragActive, setDragActive] = useState(false);
28
- const fileInputRef = useRef<HTMLInputElement>(null);
29
-
30
- const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
31
- const file = event.target.files?.[0];
32
- if (file) {
33
- setSelectedFile(file);
34
- setResults([]);
35
- }
36
- };
37
-
38
- const handleDragOver = (event: React.DragEvent) => {
39
- event.preventDefault();
40
- event.stopPropagation();
41
- setDragActive(true);
42
- };
43
-
44
- const handleDragLeave = (event: React.DragEvent) => {
45
- event.preventDefault();
46
- event.stopPropagation();
47
- setDragActive(false);
48
- };
49
-
50
- const handleDrop = (event: React.DragEvent) => {
51
- event.preventDefault();
52
- event.stopPropagation();
53
- setDragActive(false);
54
-
55
- const file = event.dataTransfer.files?.[0];
56
- if (file) {
57
- setSelectedFile(file);
58
- setResults([]);
59
- }
60
- };
61
-
62
- const handleUpload = async () => {
63
- if (!selectedFile) return;
64
-
65
- setIsUploading(true);
66
- setResults([]);
67
-
68
- try {
69
- const formData = new FormData();
70
- formData.append('file', selectedFile);
71
-
72
- const response = await fetch('/api/workflows/import', {
73
- method: 'POST',
74
- body: formData,
75
- });
76
-
77
- if (!response.ok) {
78
- const errorData = await response.json();
79
- throw new Error(errorData.message || 'Upload failed');
80
- }
81
-
82
- const data = await response.json();
83
-
84
- // Handle results
85
- if (Array.isArray(data.results)) {
86
- setResults(data.results);
87
- } else if (data.success) {
88
- setResults([{
89
- success: true,
90
- filename: data.filename || selectedFile.name,
91
- message: data.message,
92
- }]);
93
- }
94
-
95
- // Call completion callback after a delay
96
- setTimeout(() => {
97
- if (onImportComplete) {
98
- onImportComplete();
99
- }
100
- }, 1500);
101
- } catch (error) {
102
- setResults([{
103
- success: false,
104
- filename: selectedFile.name,
105
- message: error instanceof Error ? error.message : 'Unknown error',
106
- }]);
107
- } finally {
108
- setIsUploading(false);
109
- }
110
- };
111
-
112
- const handleClose = () => {
113
- setSelectedFile(null);
114
- setResults([]);
115
- onOpenChange(false);
116
- };
117
-
118
- const isValidFile = (file: File | null): boolean => {
119
- if (!file) return false;
120
- const validExtensions = ['.md', '.yaml', '.yml', '.zip'];
121
- return validExtensions.some(ext => file.name.toLowerCase().endsWith(ext));
122
- };
123
-
124
- const getFileIcon = (filename: string) => {
125
- if (filename.endsWith('.zip')) {
126
- return <Archive className="w-5 h-5 text-purple-400" />;
127
- }
128
- return <FileText className="w-5 h-5 text-blue-400" />;
129
- };
130
-
131
- return (
132
- <Modal
133
- open={open}
134
- onOpenChange={onOpenChange}
135
- title="Import Workflow"
136
- description="Upload workflow files from your computer"
137
- size="md"
138
- >
139
- <div className="p-4 space-y-4">
140
- {/* File Upload Area */}
141
- {!selectedFile && results.length === 0 && (
142
- <div
143
- onClick={() => fileInputRef.current?.click()}
144
- onDragOver={handleDragOver}
145
- onDragLeave={handleDragLeave}
146
- onDrop={handleDrop}
147
- className={`
148
- border-2 border-dashed rounded-lg p-8 text-center cursor-pointer transition-colors
149
- ${dragActive
150
- ? 'border-primary bg-primary/10'
151
- : 'border-node-border hover:border-primary/50 hover:bg-white/5'
152
- }
153
- `}
154
- >
155
- <Upload className="w-12 h-12 mx-auto mb-4 text-gray-500" />
156
- <p className="text-sm text-gray-300 mb-2">
157
- Drag and drop a file here, or click to browse
158
- </p>
159
- <p className="text-xs text-gray-500">
160
- Supported formats: .md, .yaml, .yml, .zip
161
- </p>
162
-
163
- <input
164
- ref={fileInputRef}
165
- type="file"
166
- accept=".md,.yaml,.yml,.zip"
167
- onChange={handleFileSelect}
168
- className="hidden"
169
- />
170
- </div>
171
- )}
172
-
173
- {/* Selected File */}
174
- {selectedFile && results.length === 0 && (
175
- <div className="border border-node-border rounded-lg p-4">
176
- <div className="flex items-center gap-3">
177
- {getFileIcon(selectedFile.name)}
178
- <div className="flex-1 min-w-0">
179
- <p className="text-sm font-medium text-white truncate">
180
- {selectedFile.name}
181
- </p>
182
- <p className="text-xs text-gray-500">
183
- {(selectedFile.size / 1024).toFixed(2)} KB
184
- </p>
185
- </div>
186
- {!isValidFile(selectedFile) && (
187
- <AlertCircle className="w-5 h-5 text-error flex-shrink-0" />
188
- )}
189
- </div>
190
-
191
- {!isValidFile(selectedFile) && (
192
- <p className="text-xs text-error mt-2">
193
- Invalid file type. Please select a .md, .yaml, .yml, or .zip file.
194
- </p>
195
- )}
196
- </div>
197
- )}
198
-
199
- {/* Upload Results */}
200
- {results.length > 0 && (
201
- <div className="space-y-2">
202
- <h4 className="text-sm font-medium text-white">Import Results:</h4>
203
- {results.map((result, index) => (
204
- <div
205
- key={index}
206
- className={`
207
- flex items-start gap-3 p-3 rounded-lg border
208
- ${result.success
209
- ? 'border-success/30 bg-success/10'
210
- : 'border-error/30 bg-error/10'
211
- }
212
- `}
213
- >
214
- {result.success ? (
215
- <CheckCircle className="w-5 h-5 text-success flex-shrink-0 mt-0.5" />
216
- ) : (
217
- <XCircle className="w-5 h-5 text-error flex-shrink-0 mt-0.5" />
218
- )}
219
- <div className="flex-1 min-w-0">
220
- <p className="text-sm font-medium text-white truncate">
221
- {result.filename}
222
- </p>
223
- {result.message && (
224
- <p className="text-xs text-gray-400 mt-1">
225
- {result.message}
226
- </p>
227
- )}
228
- </div>
229
- </div>
230
- ))}
231
- </div>
232
- )}
233
- </div>
234
-
235
- <ModalFooter>
236
- {results.length > 0 ? (
237
- <Button variant="secondary" onClick={handleClose}>
238
- Close
239
- </Button>
240
- ) : (
241
- <>
242
- <Button variant="secondary" onClick={handleClose} disabled={isUploading}>
243
- Cancel
244
- </Button>
245
- <Button
246
- variant="primary"
247
- onClick={handleUpload}
248
- disabled={!selectedFile || !isValidFile(selectedFile) || isUploading}
249
- >
250
- {isUploading ? 'Uploading...' : 'Import'}
251
- </Button>
252
- </>
253
- )}
254
- </ModalFooter>
255
- </Modal>
256
- );
257
- }