@dilipod/ui 0.4.8 → 0.4.9

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dilipod/ui",
3
- "version": "0.4.8",
3
+ "version": "0.4.9",
4
4
  "description": "Dilipod Design System - Shared UI components and styles",
5
5
  "author": "Dilipod <hello@dilipod.com>",
6
6
  "license": "MIT",
@@ -21,7 +21,11 @@ import {
21
21
  Copy,
22
22
  PencilSimple,
23
23
  Eye,
24
- TreeStructure
24
+ TreeStructure,
25
+ DownloadSimple,
26
+ ArrowSquareOut,
27
+ ClockCounterClockwise,
28
+ ArrowsClockwise,
25
29
  } from '@phosphor-icons/react'
26
30
 
27
31
  // Lazy load the flow visualization to avoid SSR issues
@@ -117,11 +121,25 @@ export interface WorkflowViewerProps {
117
121
  pushToN8n?: (workerId: string) => Promise<{ success: boolean; error?: string }>
118
122
  /** Pull workflow from n8n */
119
123
  pullFromN8n?: (workflowDefId: string) => Promise<{ success: boolean; error?: string; descriptionSync?: { needsUpdate: boolean; reason?: string } }>
124
+ /** Export workflow from Sim Studio (backup) */
125
+ exportFromSim?: (workflowDefId: string) => Promise<{ success: boolean; error?: string; backup?: { id: string; version: number; exportedAt: string }; workflow?: { blocksCount: number; edgesCount: number } }>
126
+ /** Get backup history for Sim workflow */
127
+ getSimBackups?: (workflowDefId: string) => Promise<{ success: boolean; error?: string; backups?: Array<{ id: string; version: number; versionLabel?: string; workflowName: string; isDeployed: boolean; exportedAt: string }> }>
128
+ /** Push workflow to Sim Studio */
129
+ pushToSim?: (workflowDefId: string) => Promise<{ success: boolean; error?: string; workflowId?: string }>
130
+ /** Pull workflow from Sim Studio */
131
+ pullFromSim?: (workflowDefId: string) => Promise<{ success: boolean; error?: string; descriptionSync?: { needsUpdate: boolean; reason?: string } }>
120
132
  /** Create new workflow */
121
133
  createWorkflow?: (data: { agent_id: string; name: string; platform: 'n8n' | 'sim'; n8n_workflow?: N8nWorkflow | null; sim_workflow?: SimWorkflow | null; is_active: boolean; is_global: boolean }) => Promise<{ success: boolean; error?: string; workflow?: { id: string } }>
122
134
  /** Load workflow template */
123
135
  loadTemplate?: (type: string, workerId: string) => Promise<{ success: boolean; error?: string; workflow?: N8nWorkflow | SimWorkflow }>
136
+ /** Switch workflow platform (e.g., n8n to Sim) */
137
+ switchPlatform?: (workflowDefId: string, targetPlatform: 'n8n' | 'sim') => Promise<{ success: boolean; error?: string; simWorkflowId?: string }>
124
138
  }
139
+ /** Sim workflow ID (for Sim Studio integration) */
140
+ simWorkflowId?: string | null
141
+ /** Sim Studio base URL for external links */
142
+ simStudioUrl?: string
125
143
  /** Custom loading component */
126
144
  loadingComponent?: ReactNode
127
145
  /** Format distance function for timestamps */
@@ -430,8 +448,10 @@ export function WorkflowViewer({
430
448
  apiHandlers,
431
449
  loadingComponent,
432
450
  formatDistance = defaultFormatDistance,
451
+ simWorkflowId,
452
+ simStudioUrl,
433
453
  }: WorkflowViewerProps) {
434
- const [viewMode, setViewMode] = useState<'summary' | 'flow' | 'json' | 'edit' | 'create'>('summary')
454
+ const [viewMode, setViewMode] = useState<'summary' | 'flow' | 'json' | 'edit' | 'create' | 'backups'>('summary')
435
455
  const [editedJson, setEditedJson] = useState('')
436
456
  const [jsonError, setJsonError] = useState<string | null>(null)
437
457
  const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null)
@@ -440,6 +460,12 @@ export function WorkflowViewer({
440
460
  const [pulling, setPulling] = useState(false)
441
461
  const [creating, setCreating] = useState(false)
442
462
  const [selectedTemplate, setSelectedTemplate] = useState<WorkflowTemplate>('blank')
463
+ const [exporting, setExporting] = useState(false)
464
+ const [backups, setBackups] = useState<Array<{ id: string; version: number; versionLabel?: string; workflowName: string; isDeployed: boolean; exportedAt: string }>>([])
465
+ const [loadingBackups, setLoadingBackups] = useState(false)
466
+ const [pushingToSim, setPushingToSim] = useState(false)
467
+ const [pullingFromSim, setPullingFromSim] = useState(false)
468
+ const [switchingPlatform, setSwitchingPlatform] = useState(false)
443
469
 
444
470
  const [localPlatform, setLocalPlatform] = useState<'n8n' | 'sim'>(platform)
445
471
  const [localIsActive, setLocalIsActive] = useState(isActive ?? true)
@@ -624,6 +650,133 @@ export function WorkflowViewer({
624
650
  }
625
651
  }
626
652
 
653
+ async function exportFromSim() {
654
+ if (!workflowDefinitionId || !simWorkflowId || !apiHandlers?.exportFromSim) {
655
+ setMessage({ type: 'error', text: 'Cannot export - no Sim workflow ID or API handler' })
656
+ return
657
+ }
658
+
659
+ setExporting(true)
660
+ setMessage(null)
661
+
662
+ try {
663
+ const result = await apiHandlers.exportFromSim(workflowDefinitionId)
664
+
665
+ if (result.success) {
666
+ const blocksCount = result.workflow?.blocksCount || 0
667
+ const edgesCount = result.workflow?.edgesCount || 0
668
+ setMessage({
669
+ type: 'success',
670
+ text: `Exported from Sim Studio (v${result.backup?.version || 1}, ${blocksCount} blocks, ${edgesCount} edges)`
671
+ })
672
+ // Refresh backups list if viewing
673
+ if (viewMode === 'backups') {
674
+ loadBackups()
675
+ }
676
+ } else {
677
+ setMessage({ type: 'error', text: result.error || 'Failed to export from Sim Studio' })
678
+ }
679
+ } catch {
680
+ setMessage({ type: 'error', text: 'Failed to export from Sim Studio' })
681
+ } finally {
682
+ setExporting(false)
683
+ }
684
+ }
685
+
686
+ async function loadBackups() {
687
+ if (!workflowDefinitionId || !apiHandlers?.getSimBackups) {
688
+ return
689
+ }
690
+
691
+ setLoadingBackups(true)
692
+
693
+ try {
694
+ const result = await apiHandlers.getSimBackups(workflowDefinitionId)
695
+ if (result.success && result.backups) {
696
+ setBackups(result.backups)
697
+ }
698
+ } catch {
699
+ console.error('Failed to load backups')
700
+ } finally {
701
+ setLoadingBackups(false)
702
+ }
703
+ }
704
+
705
+ function showBackups() {
706
+ setViewMode('backups')
707
+ loadBackups()
708
+ }
709
+
710
+ function openInSimStudio() {
711
+ if (simStudioUrl && simWorkflowId) {
712
+ window.open(`${simStudioUrl}/w/${simWorkflowId}`, '_blank')
713
+ }
714
+ }
715
+
716
+ async function pushToSim() {
717
+ if (!workflowDefinitionId || !apiHandlers?.pushToSim) {
718
+ setMessage({ type: 'error', text: 'Cannot push - no workflow definition ID or API handler' })
719
+ return
720
+ }
721
+
722
+ setPushingToSim(true)
723
+ setMessage(null)
724
+
725
+ try {
726
+ const result = await apiHandlers.pushToSim(workflowDefinitionId)
727
+
728
+ if (result.success) {
729
+ setMessage({
730
+ type: 'success',
731
+ text: result.workflowId
732
+ ? `Pushed to Sim Studio (workflow: ${result.workflowId})`
733
+ : 'Pushed to Sim Studio successfully'
734
+ })
735
+ // Reload to show updated state
736
+ setTimeout(() => window.location.reload(), 1500)
737
+ } else {
738
+ setMessage({ type: 'error', text: result.error || 'Failed to push to Sim Studio' })
739
+ }
740
+ } catch {
741
+ setMessage({ type: 'error', text: 'Failed to push to Sim Studio' })
742
+ } finally {
743
+ setPushingToSim(false)
744
+ }
745
+ }
746
+
747
+ async function pullFromSim() {
748
+ if (!workflowDefinitionId || !simWorkflowId || !apiHandlers?.pullFromSim) {
749
+ setMessage({ type: 'error', text: 'Cannot pull - no Sim workflow ID or API handler' })
750
+ return
751
+ }
752
+
753
+ setPullingFromSim(true)
754
+ setMessage(null)
755
+
756
+ try {
757
+ const result = await apiHandlers.pullFromSim(workflowDefinitionId)
758
+
759
+ if (result.success) {
760
+ if (result.descriptionSync?.needsUpdate) {
761
+ setMessage({
762
+ type: 'success',
763
+ text: `Pulled from Sim Studio. Note: ${result.descriptionSync.reason || 'Worker description may need updating.'}`
764
+ })
765
+ } else {
766
+ setMessage({ type: 'success', text: 'Pulled from Sim Studio successfully' })
767
+ }
768
+ // Reload to show updated state
769
+ setTimeout(() => window.location.reload(), 1500)
770
+ } else {
771
+ setMessage({ type: 'error', text: result.error || 'Failed to pull from Sim Studio' })
772
+ }
773
+ } catch {
774
+ setMessage({ type: 'error', text: 'Failed to pull from Sim Studio' })
775
+ } finally {
776
+ setPullingFromSim(false)
777
+ }
778
+ }
779
+
627
780
  function startCreating() {
628
781
  if (internalWorkerType) {
629
782
  setSelectedTemplate(internalWorkerType as WorkflowTemplate)
@@ -677,6 +830,37 @@ export function WorkflowViewer({
677
830
  }
678
831
  }
679
832
 
833
+ async function switchPlatform(targetPlatform: 'n8n' | 'sim') {
834
+ if (!workflowDefinitionId || !apiHandlers?.switchPlatform) {
835
+ setMessage({ type: 'error', text: 'Cannot switch platform - no workflow definition ID or API handler' })
836
+ return
837
+ }
838
+
839
+ setSwitchingPlatform(true)
840
+ setMessage(null)
841
+
842
+ try {
843
+ const result = await apiHandlers.switchPlatform(workflowDefinitionId, targetPlatform)
844
+
845
+ if (result.success) {
846
+ setMessage({
847
+ type: 'success',
848
+ text: targetPlatform === 'sim'
849
+ ? `Switched to Sim Studio${result.simWorkflowId ? ` (workflow: ${result.simWorkflowId})` : ''}`
850
+ : 'Switched to n8n'
851
+ })
852
+ // Reload to show updated state
853
+ setTimeout(() => window.location.reload(), 1500)
854
+ } else {
855
+ setMessage({ type: 'error', text: result.error || 'Failed to switch platform' })
856
+ }
857
+ } catch {
858
+ setMessage({ type: 'error', text: 'Failed to switch platform' })
859
+ } finally {
860
+ setSwitchingPlatform(false)
861
+ }
862
+ }
863
+
680
864
  async function loadTemplate() {
681
865
  if (!workerId || !workerName || !apiHandlers?.loadTemplate) {
682
866
  setJsonError('Worker information or API handler required to generate template.')
@@ -904,16 +1088,22 @@ export function WorkflowViewer({
904
1088
  {lastSynced && (
905
1089
  <span>Synced {formatDistance(new Date(lastSynced), { addSuffix: true })}</span>
906
1090
  )}
907
- {webhookUrl && (
1091
+ </div>
1092
+
1093
+ {/* Webhook URL display */}
1094
+ {webhookUrl && (
1095
+ <div className="flex items-center gap-2 p-2 bg-muted/50 rounded border border-border">
1096
+ <WebhooksLogo size={14} className="text-muted-foreground flex-shrink-0" />
1097
+ <code className="text-xs font-mono text-foreground truncate flex-1">{webhookUrl}</code>
908
1098
  <button
909
1099
  onClick={() => copyToClipboard(webhookUrl, 'Webhook URL copied')}
910
- className="flex items-center gap-1 hover:text-foreground"
1100
+ className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground flex-shrink-0"
911
1101
  >
912
1102
  <Copy size={12} />
913
- Copy webhook
1103
+ Copy
914
1104
  </button>
915
- )}
916
- </div>
1105
+ </div>
1106
+ )}
917
1107
 
918
1108
  {(allowPlatformChange || allowStatusChange) && workflowDefinitionId && hasUnsavedChanges && apiHandlers?.saveSettings && (
919
1109
  <div className="p-3 bg-amber-50 border border-amber-200 rounded-lg space-y-3">
@@ -966,8 +1156,9 @@ export function WorkflowViewer({
966
1156
  </Alert>
967
1157
  )}
968
1158
 
1159
+ {/* n8n sync buttons */}
969
1160
  {editable && workerId && platform === 'n8n' && viewMode !== 'edit' && apiHandlers?.pushToN8n && (
970
- <div className="flex gap-2">
1161
+ <div className="flex flex-wrap gap-2">
971
1162
  <Button onClick={pushToN8n} disabled={syncing} variant="primary" size="sm" icon={<CloudArrowUp size={16} />}>
972
1163
  {syncing ? 'Pushing...' : 'Push to n8n'}
973
1164
  </Button>
@@ -976,6 +1167,80 @@ export function WorkflowViewer({
976
1167
  {pulling ? 'Pulling...' : 'Pull from n8n'}
977
1168
  </Button>
978
1169
  )}
1170
+ {/* Switch to Sim Studio button */}
1171
+ {workflowDefinitionId && apiHandlers?.switchPlatform && (
1172
+ <Button
1173
+ onClick={() => switchPlatform('sim')}
1174
+ disabled={switchingPlatform}
1175
+ variant="outline"
1176
+ size="sm"
1177
+ icon={<ArrowsClockwise size={16} />}
1178
+ className="border-purple-300 text-purple-700 hover:bg-purple-50 hover:border-purple-400"
1179
+ >
1180
+ {switchingPlatform ? 'Switching...' : 'Switch to Sim Studio'}
1181
+ </Button>
1182
+ )}
1183
+ </div>
1184
+ )}
1185
+
1186
+ {/* Sim Studio sync buttons */}
1187
+ {platform === 'sim' && viewMode !== 'edit' && (
1188
+ <div className="flex flex-wrap gap-2">
1189
+ {/* Push/Pull buttons (require workflow definition) */}
1190
+ {editable && workflowDefinitionId && apiHandlers?.pushToSim && (
1191
+ <Button
1192
+ onClick={pushToSim}
1193
+ disabled={pushingToSim}
1194
+ variant="primary"
1195
+ size="sm"
1196
+ icon={<CloudArrowUp size={16} />}
1197
+ >
1198
+ {pushingToSim ? 'Pushing...' : 'Push to Sim'}
1199
+ </Button>
1200
+ )}
1201
+ {simWorkflowId && apiHandlers?.pullFromSim && (
1202
+ <Button
1203
+ onClick={pullFromSim}
1204
+ disabled={pullingFromSim}
1205
+ variant="outline"
1206
+ size="sm"
1207
+ icon={<CloudArrowDown size={16} />}
1208
+ >
1209
+ {pullingFromSim ? 'Pulling...' : 'Pull from Sim'}
1210
+ </Button>
1211
+ )}
1212
+ {/* Export/Backup buttons (require sim workflow id) */}
1213
+ {simWorkflowId && apiHandlers?.exportFromSim && (
1214
+ <Button
1215
+ onClick={exportFromSim}
1216
+ disabled={exporting}
1217
+ variant="outline"
1218
+ size="sm"
1219
+ icon={<DownloadSimple size={16} />}
1220
+ >
1221
+ {exporting ? 'Exporting...' : 'Export Backup'}
1222
+ </Button>
1223
+ )}
1224
+ {simWorkflowId && apiHandlers?.getSimBackups && (
1225
+ <Button
1226
+ onClick={showBackups}
1227
+ variant="outline"
1228
+ size="sm"
1229
+ icon={<ClockCounterClockwise size={16} />}
1230
+ >
1231
+ History
1232
+ </Button>
1233
+ )}
1234
+ {simStudioUrl && simWorkflowId && (
1235
+ <Button
1236
+ onClick={openInSimStudio}
1237
+ variant="outline"
1238
+ size="sm"
1239
+ icon={<ArrowSquareOut size={16} />}
1240
+ >
1241
+ Open in Sim
1242
+ </Button>
1243
+ )}
979
1244
  </div>
980
1245
  )}
981
1246
 
@@ -998,17 +1263,20 @@ export function WorkflowViewer({
998
1263
  </p>
999
1264
  </div>
1000
1265
  ) : viewMode === 'json' ? (
1001
- <div className="relative">
1002
- <Button
1003
- onClick={() => copyToClipboard(JSON.stringify(workflow, null, 2), 'JSON copied')}
1004
- variant="outline"
1005
- size="sm"
1006
- className="absolute top-2 right-2 z-10"
1007
- icon={<Copy size={14} />}
1008
- >
1009
- Copy
1010
- </Button>
1011
- <pre className="p-4 bg-slate-900 text-slate-100 rounded-lg text-xs overflow-auto max-h-[500px] font-mono">
1266
+ <div className="relative rounded-sm overflow-hidden border border-border">
1267
+ <div className="flex items-center justify-between px-4 py-2 bg-[var(--black)] border-b border-gray-800">
1268
+ <span className="text-xs font-medium text-gray-400">JSON</span>
1269
+ <Button
1270
+ onClick={() => copyToClipboard(JSON.stringify(workflow, null, 2), 'JSON copied')}
1271
+ variant="outline"
1272
+ size="sm"
1273
+ className="h-7 bg-transparent border-gray-600 text-gray-300 hover:bg-gray-800 hover:text-white hover:border-gray-500"
1274
+ icon={<Copy size={12} />}
1275
+ >
1276
+ Copy
1277
+ </Button>
1278
+ </div>
1279
+ <pre className="p-4 bg-[var(--black)] text-gray-100 text-xs overflow-auto max-h-[500px] font-mono">
1012
1280
  {JSON.stringify(workflow, null, 2)}
1013
1281
  </pre>
1014
1282
  </div>
@@ -1018,6 +1286,54 @@ export function WorkflowViewer({
1018
1286
  <WorkflowFlow workflow={workflow as any} height={380} />
1019
1287
  </Suspense>
1020
1288
  )
1289
+ ) : viewMode === 'backups' ? (
1290
+ <div className="space-y-3">
1291
+ <div className="flex items-center justify-between">
1292
+ <h4 className="text-sm font-medium">Backup History</h4>
1293
+ <Button
1294
+ onClick={() => setViewMode('summary')}
1295
+ variant="outline"
1296
+ size="sm"
1297
+ >
1298
+ Back to Summary
1299
+ </Button>
1300
+ </div>
1301
+ {loadingBackups ? (
1302
+ <div className="py-8 text-center text-muted-foreground">Loading backups...</div>
1303
+ ) : backups.length === 0 ? (
1304
+ <div className="py-8 text-center text-muted-foreground">
1305
+ <ClockCounterClockwise size={32} className="mx-auto mb-2 opacity-50" />
1306
+ <p>No backups yet</p>
1307
+ <p className="text-xs mt-1">Click &quot;Export from Sim&quot; to create a backup</p>
1308
+ </div>
1309
+ ) : (
1310
+ <div className="space-y-2">
1311
+ {backups.map((backup) => (
1312
+ <div
1313
+ key={backup.id}
1314
+ className="flex items-center justify-between p-3 bg-muted/50 rounded border border-border"
1315
+ >
1316
+ <div className="flex items-center gap-3">
1317
+ <div className="flex items-center justify-center w-8 h-8 rounded bg-primary/10 text-primary text-sm font-semibold">
1318
+ v{backup.version}
1319
+ </div>
1320
+ <div>
1321
+ <p className="text-sm font-medium">
1322
+ {backup.versionLabel || backup.workflowName}
1323
+ </p>
1324
+ <p className="text-xs text-muted-foreground">
1325
+ {formatDistance(new Date(backup.exportedAt), { addSuffix: true })}
1326
+ {backup.isDeployed && (
1327
+ <Badge variant="success" size="sm" className="ml-2">Deployed</Badge>
1328
+ )}
1329
+ </p>
1330
+ </div>
1331
+ </div>
1332
+ </div>
1333
+ ))}
1334
+ </div>
1335
+ )}
1336
+ </div>
1021
1337
  ) : (
1022
1338
  platform === 'n8n'
1023
1339
  ? <N8nWorkflowSummary workflow={workflow as N8nWorkflow} showFlow={false} />