@flowdrop/flowdrop 1.15.0 → 2.0.0-beta.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 (235) hide show
  1. package/CHANGELOG.md +508 -0
  2. package/MIGRATION-2.0.md +629 -0
  3. package/README.md +23 -23
  4. package/dist/adapters/WorkflowAdapter.d.ts +1 -1
  5. package/dist/adapters/WorkflowAdapter.js +14 -8
  6. package/dist/adapters/agentspec/AgentSpecAdapter.js +7 -7
  7. package/dist/api/enhanced-client.js +6 -11
  8. package/dist/chat/batchFeedback.d.ts +39 -0
  9. package/dist/chat/batchFeedback.js +51 -0
  10. package/dist/commands/executor.js +15 -1
  11. package/dist/commands/storeIntegration.svelte.d.ts +4 -1
  12. package/dist/commands/storeIntegration.svelte.js +26 -21
  13. package/dist/commands/types.d.ts +2 -0
  14. package/dist/components/App.svelte +163 -192
  15. package/dist/components/App.svelte.d.ts +47 -8
  16. package/dist/components/ConfigForm.svelte +77 -49
  17. package/dist/components/ConfigModal.svelte +7 -2
  18. package/dist/components/ConnectionLine.svelte +4 -2
  19. package/dist/components/Navbar.svelte +61 -1
  20. package/dist/components/NodeSidebar.svelte +27 -45
  21. package/dist/components/NodeStatusOverlay.svelte +94 -6
  22. package/dist/components/NodeSwapPicker.svelte +10 -8
  23. package/dist/components/PipelineStatus.svelte +22 -68
  24. package/dist/components/PipelineStatus.svelte.d.ts +3 -0
  25. package/dist/components/PortCoordinateTracker.svelte +5 -6
  26. package/dist/components/SchemaForm.stories.svelte +1 -3
  27. package/dist/components/SchemaForm.svelte +22 -27
  28. package/dist/components/SchemaForm.svelte.d.ts +0 -8
  29. package/dist/components/SettingsModal.svelte +8 -3
  30. package/dist/components/SettingsPanel.svelte +20 -4
  31. package/dist/components/SwapMappingEditor.svelte +67 -49
  32. package/dist/components/SwapMappingEditor.svelte.d.ts +0 -2
  33. package/dist/components/UniversalNode.svelte +9 -7
  34. package/dist/components/WorkflowEditor.svelte +121 -111
  35. package/dist/components/WorkflowEditor.svelte.d.ts +21 -10
  36. package/dist/components/chat/AIChatPanel.svelte +98 -89
  37. package/dist/components/chat/AIChatPanel.svelte.d.ts +0 -4
  38. package/dist/components/chat/CommandPreview.svelte +2 -1
  39. package/dist/components/console/CommandConsole.svelte +7 -5
  40. package/dist/components/console/ConsoleAutocomplete.svelte +10 -11
  41. package/dist/components/console/ConsoleAutocomplete.svelte.d.ts +6 -0
  42. package/dist/components/console/ConsoleInput.svelte +15 -6
  43. package/dist/components/console/ConsoleOutput.svelte +2 -1
  44. package/dist/components/form/FormArray.svelte +5 -9
  45. package/dist/components/form/FormArray.svelte.d.ts +2 -1
  46. package/dist/components/form/FormAutocomplete.svelte +16 -15
  47. package/dist/components/form/FormField.svelte +4 -2
  48. package/dist/components/form/FormFieldLight.svelte +34 -3
  49. package/dist/components/form/FormFieldLight.svelte.d.ts +12 -0
  50. package/dist/components/form/FormMarkdownEditor.svelte +9 -4
  51. package/dist/components/form/FormRangeField.svelte +1 -0
  52. package/dist/components/form/FormTemplateEditor.svelte +11 -3
  53. package/dist/components/form/FormToggle.svelte +5 -12
  54. package/dist/components/form/FormToggle.svelte.d.ts +4 -2
  55. package/dist/components/form/FormUISchemaRenderer.svelte +3 -1
  56. package/dist/components/form/templateAutocomplete.js +1 -5
  57. package/dist/components/form/types.d.ts +1 -14
  58. package/dist/components/interrupt/FormPrompt.svelte +3 -2
  59. package/dist/components/interrupt/InterruptBubble.svelte +25 -17
  60. package/dist/components/interrupt/ReviewPrompt.svelte +10 -3
  61. package/dist/components/interrupt/TextInputPrompt.svelte +2 -1
  62. package/dist/components/layouts/MainLayout.svelte +20 -13
  63. package/dist/components/layouts/MainLayout.svelte.d.ts +4 -0
  64. package/dist/components/nodes/AtomNode.svelte +17 -5
  65. package/dist/components/nodes/GatewayNode.svelte +19 -10
  66. package/dist/components/nodes/IdeaNode.svelte +7 -0
  67. package/dist/components/nodes/SimpleNode.svelte +11 -6
  68. package/dist/components/nodes/SquareNode.svelte +15 -8
  69. package/dist/components/nodes/TerminalNode.svelte +9 -4
  70. package/dist/components/nodes/ToolNode.svelte +7 -1
  71. package/dist/components/nodes/WorkflowNode.svelte +16 -7
  72. package/dist/components/playground/ChatInput.svelte +11 -14
  73. package/dist/components/playground/ChatPanel.svelte +6 -49
  74. package/dist/components/playground/ChatPanel.svelte.d.ts +0 -14
  75. package/dist/components/playground/ControlPanel.svelte +134 -123
  76. package/dist/components/playground/ControlPanel.svelte.d.ts +3 -0
  77. package/dist/components/playground/ExecutionLogs.svelte +11 -9
  78. package/dist/components/playground/InputCollector.svelte +11 -9
  79. package/dist/components/playground/MessageStream.svelte +17 -23
  80. package/dist/components/playground/PipelineKanbanView.svelte +69 -8
  81. package/dist/components/playground/PipelineKanbanView.svelte.d.ts +2 -0
  82. package/dist/components/playground/PipelinePanel.svelte +31 -8
  83. package/dist/components/playground/PipelinePanel.svelte.d.ts +2 -0
  84. package/dist/components/playground/PipelineTableView.svelte +188 -44
  85. package/dist/components/playground/PipelineTableView.svelte.d.ts +2 -0
  86. package/dist/components/playground/Playground.svelte +154 -105
  87. package/dist/components/playground/Playground.svelte.d.ts +5 -0
  88. package/dist/components/playground/PlaygroundApp.svelte +11 -1
  89. package/dist/components/playground/PlaygroundApp.svelte.d.ts +6 -0
  90. package/dist/components/playground/PlaygroundModal.svelte +18 -3
  91. package/dist/components/playground/PlaygroundModal.svelte.d.ts +6 -0
  92. package/dist/components/playground/PlaygroundStudio.svelte +40 -32
  93. package/dist/components/playground/PlaygroundStudio.svelte.d.ts +6 -0
  94. package/dist/components/playground/SessionManager.svelte +9 -12
  95. package/dist/components/playground/pipelineViewUtils.svelte.d.ts +30 -1
  96. package/dist/components/playground/pipelineViewUtils.svelte.js +40 -3
  97. package/dist/config/endpoints.d.ts +23 -7
  98. package/dist/config/endpoints.js +30 -10
  99. package/dist/core/index.d.ts +5 -6
  100. package/dist/core/index.js +8 -12
  101. package/dist/display/index.d.ts +6 -3
  102. package/dist/display/index.js +7 -5
  103. package/dist/editor/index.d.ts +20 -21
  104. package/dist/editor/index.js +26 -36
  105. package/dist/form/code.d.ts +25 -15
  106. package/dist/form/code.js +44 -41
  107. package/dist/form/fieldRegistry.d.ts +17 -13
  108. package/dist/form/fieldRegistry.js +32 -12
  109. package/dist/form/full.d.ts +19 -14
  110. package/dist/form/full.js +26 -28
  111. package/dist/form/index.d.ts +3 -4
  112. package/dist/form/index.js +6 -5
  113. package/dist/form/markdown.d.ts +13 -8
  114. package/dist/form/markdown.js +22 -23
  115. package/dist/helpers/proximityConnect.d.ts +3 -2
  116. package/dist/helpers/proximityConnect.js +2 -5
  117. package/dist/helpers/workflowEditorHelper.d.ts +14 -5
  118. package/dist/helpers/workflowEditorHelper.js +28 -25
  119. package/dist/index.d.ts +28 -24
  120. package/dist/index.js +27 -50
  121. package/dist/messages/defaults.d.ts +2 -5
  122. package/dist/messages/defaults.js +3 -6
  123. package/dist/messages/index.d.ts +0 -1
  124. package/dist/messages/index.js +0 -1
  125. package/dist/mocks/app-forms.d.ts +6 -2
  126. package/dist/mocks/app-forms.js +11 -4
  127. package/dist/openapi/v1/openapi.yaml +3 -3
  128. package/dist/playground/index.d.ts +4 -5
  129. package/dist/playground/index.js +4 -32
  130. package/dist/playground/mount.d.ts +25 -0
  131. package/dist/playground/mount.js +50 -20
  132. package/dist/registry/{BaseRegistry.d.ts → BaseRegistry.svelte.d.ts} +22 -1
  133. package/dist/registry/{BaseRegistry.js → BaseRegistry.svelte.js} +37 -1
  134. package/dist/registry/builtinFormats.d.ts +9 -18
  135. package/dist/registry/builtinFormats.js +9 -39
  136. package/dist/registry/builtinNodeTypes.d.ts +53 -0
  137. package/dist/registry/builtinNodeTypes.js +67 -0
  138. package/dist/registry/builtinNodes.d.ts +2 -64
  139. package/dist/registry/builtinNodes.js +7 -103
  140. package/dist/registry/index.d.ts +3 -4
  141. package/dist/registry/index.js +4 -6
  142. package/dist/registry/nodeComponentRegistry.d.ts +182 -15
  143. package/dist/registry/nodeComponentRegistry.js +235 -17
  144. package/dist/registry/workflowFormatRegistry.d.ts +14 -9
  145. package/dist/registry/workflowFormatRegistry.js +24 -8
  146. package/dist/{schema → schemas}/index.d.ts +2 -2
  147. package/dist/{schema → schemas}/index.js +2 -2
  148. package/dist/schemas/v1/workflow.schema.json +3 -3
  149. package/dist/services/agentSpecExecutionService.d.ts +0 -2
  150. package/dist/services/agentSpecExecutionService.js +0 -3
  151. package/dist/services/apiVariableService.d.ts +2 -1
  152. package/dist/services/apiVariableService.js +16 -47
  153. package/dist/services/autoSaveService.d.ts +7 -0
  154. package/dist/services/autoSaveService.js +6 -4
  155. package/dist/services/categoriesApi.js +3 -6
  156. package/dist/services/chatService.d.ts +9 -4
  157. package/dist/services/chatService.js +23 -28
  158. package/dist/services/draftStorage.d.ts +129 -13
  159. package/dist/services/draftStorage.js +185 -37
  160. package/dist/services/dynamicSchemaService.d.ts +2 -1
  161. package/dist/services/dynamicSchemaService.js +5 -22
  162. package/dist/services/globalSave.d.ts +13 -12
  163. package/dist/services/globalSave.js +29 -51
  164. package/dist/services/historyService.d.ts +9 -3
  165. package/dist/services/historyService.js +9 -3
  166. package/dist/services/interruptService.d.ts +15 -9
  167. package/dist/services/interruptService.js +35 -37
  168. package/dist/services/nodeExecutionService.d.ts +18 -3
  169. package/dist/services/nodeExecutionService.js +71 -45
  170. package/dist/services/playgroundService.d.ts +16 -10
  171. package/dist/services/playgroundService.js +42 -43
  172. package/dist/services/portConfigApi.js +3 -6
  173. package/dist/services/settingsService.d.ts +9 -4
  174. package/dist/services/settingsService.js +23 -12
  175. package/dist/services/variableService.d.ts +2 -1
  176. package/dist/services/variableService.js +2 -2
  177. package/dist/services/workflowStorage.js +6 -6
  178. package/dist/stores/apiContext.d.ts +56 -0
  179. package/dist/stores/apiContext.js +80 -0
  180. package/dist/stores/categoriesStore.svelte.d.ts +28 -23
  181. package/dist/stores/categoriesStore.svelte.js +69 -64
  182. package/dist/stores/getInstance.svelte.d.ts +39 -0
  183. package/dist/stores/getInstance.svelte.js +65 -0
  184. package/dist/stores/historyStore.svelte.d.ts +77 -93
  185. package/dist/stores/historyStore.svelte.js +134 -160
  186. package/dist/stores/instanceContainer.svelte.d.ts +111 -0
  187. package/dist/stores/instanceContainer.svelte.js +114 -0
  188. package/dist/stores/interruptStore.svelte.d.ts +112 -82
  189. package/dist/stores/interruptStore.svelte.js +253 -226
  190. package/dist/stores/pipelinePanelStore.svelte.d.ts +27 -3
  191. package/dist/stores/pipelinePanelStore.svelte.js +61 -14
  192. package/dist/stores/playgroundStore.svelte.d.ts +169 -222
  193. package/dist/stores/playgroundStore.svelte.js +513 -580
  194. package/dist/stores/portCoordinateStore.svelte.d.ts +57 -51
  195. package/dist/stores/portCoordinateStore.svelte.js +109 -98
  196. package/dist/stores/settingsStore.svelte.d.ts +4 -1
  197. package/dist/stores/settingsStore.svelte.js +47 -12
  198. package/dist/stores/workflowStore.svelte.d.ts +178 -213
  199. package/dist/stores/workflowStore.svelte.js +449 -501
  200. package/dist/stories/EdgeDecorator.svelte +5 -2
  201. package/dist/stories/NodeDecorator.svelte +5 -3
  202. package/dist/svelte-app.d.ts +60 -10
  203. package/dist/svelte-app.js +159 -54
  204. package/dist/types/auth.d.ts +9 -51
  205. package/dist/types/auth.js +4 -54
  206. package/dist/types/events.d.ts +6 -3
  207. package/dist/types/index.d.ts +37 -5
  208. package/dist/types/index.js +0 -1
  209. package/dist/types/navbar.d.ts +7 -0
  210. package/dist/types/playground.d.ts +18 -3
  211. package/dist/types/settings.d.ts +13 -0
  212. package/dist/types/settings.js +1 -0
  213. package/dist/utils/colors.d.ts +47 -21
  214. package/dist/utils/colors.js +69 -68
  215. package/dist/utils/connections.d.ts +9 -15
  216. package/dist/utils/connections.js +13 -32
  217. package/dist/utils/duration.d.ts +13 -0
  218. package/dist/utils/duration.js +45 -0
  219. package/dist/utils/edgeStyling.js +9 -5
  220. package/dist/utils/fetchWithAuth.d.ts +36 -15
  221. package/dist/utils/fetchWithAuth.js +53 -23
  222. package/dist/utils/icons.d.ts +5 -2
  223. package/dist/utils/icons.js +6 -5
  224. package/dist/utils/nodeSwap.d.ts +6 -2
  225. package/dist/utils/nodeSwap.js +62 -126
  226. package/dist/utils/nodeTypes.d.ts +17 -8
  227. package/dist/utils/nodeTypes.js +27 -20
  228. package/dist/utils/performanceUtils.js +7 -0
  229. package/package.json +7 -5
  230. package/dist/messages/deprecation.d.ts +0 -20
  231. package/dist/messages/deprecation.js +0 -33
  232. package/dist/registry/plugin.d.ts +0 -215
  233. package/dist/registry/plugin.js +0 -249
  234. package/dist/services/api.d.ts +0 -129
  235. package/dist/services/api.js +0 -217
@@ -42,21 +42,25 @@
42
42
  getStatusTextColor,
43
43
  getStatusBackgroundColor
44
44
  } from '../../utils/nodeStatus.js';
45
+ import { formatMicroseconds } from '../../utils/duration.js';
45
46
  import type { NodeStatus } from './pipelineViewUtils.svelte.js';
46
47
  import type { Workflow, WorkflowNode } from '../../types/index.js';
47
48
  import type { EndpointConfig } from '../../config/endpoints.js';
49
+ import type { AuthProvider } from '../../types/auth.js';
48
50
 
49
51
  interface Props {
50
52
  pipelineId: string;
51
53
  workflow: Workflow;
52
54
  endpointConfig: EndpointConfig;
55
+ authProvider?: AuthProvider;
53
56
  refreshTrigger?: number;
54
57
  }
55
58
 
56
- let { pipelineId, workflow, endpointConfig, refreshTrigger = 0 }: Props = $props();
59
+ let { pipelineId, workflow, endpointConfig, authProvider, refreshTrigger = 0 }: Props = $props();
57
60
 
58
- // svelte-ignore state_referenced_locally — endpointConfig is consumed once to build the API client; it must be stable
59
- const fetcher = createPipelineDataFetcher(() => pipelineId, endpointConfig);
61
+ // endpointConfig is consumed once to build the API client; it must be stable
62
+ // svelte-ignore state_referenced_locally
63
+ const fetcher = createPipelineDataFetcher(() => pipelineId, endpointConfig, authProvider);
60
64
 
61
65
  $effect(() => {
62
66
  if (refreshTrigger <= 0) return;
@@ -65,8 +69,13 @@
65
69
  });
66
70
 
67
71
  interface CardItem {
68
- node: WorkflowNode;
72
+ /** Stable key: job id, or node id for nodes without a job yet */
73
+ key: string;
74
+ label: string;
75
+ typeId: string;
69
76
  status: NodeStatus;
77
+ /** Duration in microseconds, for finished jobs */
78
+ durationUs?: number | null;
70
79
  }
71
80
 
72
81
  const columnedNodes = $derived.by(() => {
@@ -85,10 +94,37 @@
85
94
  nodesByColumn.set(col.key, []);
86
95
  }
87
96
 
97
+ const nodesById = new Map<string, WorkflowNode>(workflow.nodes.map((node) => [node.id, node]));
98
+
99
+ // One card per job: loop iterations create multiple jobs for the same
100
+ // node, and each deserves its own card (label carries the #N suffix).
101
+ const nodesWithJobs = new Set<string>();
102
+ for (const job of fetcher.jobs) {
103
+ const node = nodesById.get(job.nodeId);
104
+ if (!node) continue;
105
+ nodesWithJobs.add(job.nodeId);
106
+ const status = resolveStatus({ status: job.status });
107
+ const colKey = statusToColumn.get(status) ?? fallbackKey;
108
+ nodesByColumn.get(colKey)?.push({
109
+ key: job.id,
110
+ label: job.label || node.data.label,
111
+ typeId: node.data.metadata.id,
112
+ status,
113
+ durationUs: job.executionTimeUs
114
+ });
115
+ }
116
+
117
+ // Nodes without a job yet keep a single card (pending / not reached).
88
118
  for (const node of workflow.nodes) {
119
+ if (nodesWithJobs.has(node.id)) continue;
89
120
  const status = resolveStatus(fetcher.nodeStatusMap[node.id]);
90
121
  const colKey = statusToColumn.get(status) ?? fallbackKey;
91
- nodesByColumn.get(colKey)?.push({ node, status });
122
+ nodesByColumn.get(colKey)?.push({
123
+ key: node.id,
124
+ label: node.data.label,
125
+ typeId: node.data.metadata.id,
126
+ status
127
+ });
92
128
  }
93
129
 
94
130
  return { columns, nodesByColumn };
@@ -122,11 +158,11 @@
122
158
  <span class="pipeline-kanban__col-count">{items.length}</span>
123
159
  </div>
124
160
  <div class="pipeline-kanban__cards">
125
- {#each items as { node, status } (node.id)}
161
+ {#each items as { key, label, typeId, status, durationUs } (key)}
126
162
  <div class="pipeline-kanban__card">
127
163
  <div class="pipeline-kanban__card-body">
128
164
  <div class="pipeline-kanban__card-top">
129
- <span class="pipeline-kanban__card-label">{node.data.label}</span>
165
+ <span class="pipeline-kanban__card-label">{label}</span>
130
166
  {#if showStatusPill}
131
167
  <span
132
168
  class="pipeline-kanban__card-status"
@@ -137,7 +173,14 @@
137
173
  >
138
174
  {/if}
139
175
  </div>
140
- <span class="pipeline-kanban__card-type">{node.data.metadata.id}</span>
176
+ <div class="pipeline-kanban__card-meta">
177
+ <span class="pipeline-kanban__card-type">{typeId}</span>
178
+ {#if durationUs != null}
179
+ <span class="pipeline-kanban__card-duration"
180
+ >{formatMicroseconds(durationUs)}</span
181
+ >
182
+ {/if}
183
+ </div>
141
184
  </div>
142
185
  </div>
143
186
  {/each}
@@ -296,12 +339,30 @@
296
339
  flex-shrink: 0;
297
340
  }
298
341
 
342
+ .pipeline-kanban__card-meta {
343
+ display: flex;
344
+ align-items: center;
345
+ gap: var(--fd-space-xs);
346
+ min-width: 0;
347
+ }
348
+
299
349
  .pipeline-kanban__card-type {
300
350
  color: var(--fd-muted-foreground);
301
351
  font-size: var(--fd-text-2xs);
302
352
  overflow: hidden;
303
353
  text-overflow: ellipsis;
304
354
  white-space: nowrap;
355
+ flex: 1;
356
+ min-width: 0;
357
+ }
358
+
359
+ .pipeline-kanban__card-duration {
360
+ color: var(--fd-muted-foreground);
361
+ font-size: var(--fd-text-2xs);
362
+ font-family: var(--fd-font-mono, monospace);
363
+ font-variant-numeric: tabular-nums;
364
+ white-space: nowrap;
365
+ flex-shrink: 0;
305
366
  }
306
367
 
307
368
  .pipeline-kanban__empty {
@@ -1,9 +1,11 @@
1
1
  import type { Workflow } from '../../types/index.js';
2
2
  import type { EndpointConfig } from '../../config/endpoints.js';
3
+ import type { AuthProvider } from '../../types/auth.js';
3
4
  interface Props {
4
5
  pipelineId: string;
5
6
  workflow: Workflow;
6
7
  endpointConfig: EndpointConfig;
8
+ authProvider?: AuthProvider;
7
9
  refreshTrigger?: number;
8
10
  }
9
11
  declare const PipelineKanbanView: import("svelte").Component<Props, {}, "">;
@@ -1,5 +1,5 @@
1
1
  <script module lang="ts">
2
- const VIEW_MODE_KEY = 'fd-pipeline-view-mode';
2
+ const VIEW_MODE_KEY_BASE = 'fd-pipeline-view-mode';
3
3
  const BUILTIN_VIEWS = ['graph', 'kanban', 'table'] as const;
4
4
  // `string & {}` preserves autocomplete for built-in values while still accepting arbitrary strings from extraViews.
5
5
  type ViewMode = (typeof BUILTIN_VIEWS)[number] | (string & {});
@@ -7,6 +7,7 @@
7
7
 
8
8
  <script lang="ts">
9
9
  import { onMount } from 'svelte';
10
+ import { getInstance } from '../../stores/getInstance.svelte.js';
10
11
  import PipelineStatus from '../PipelineStatus.svelte';
11
12
  import PipelineKanbanView from './PipelineKanbanView.svelte';
12
13
  import PipelineTableView from './PipelineTableView.svelte';
@@ -15,12 +16,14 @@
15
16
  import { logger } from '../../utils/logger.js';
16
17
  import type { Workflow, PipelineViewDef } from '../../types/index.js';
17
18
  import type { EndpointConfig } from '../../config/endpoints.js';
19
+ import type { AuthProvider } from '../../types/auth.js';
18
20
  import type { PlaygroundExecution } from '../../types/playground.js';
19
21
 
20
22
  interface Props {
21
23
  pipelineId: string | null;
22
24
  workflow: Workflow;
23
25
  endpointConfig: EndpointConfig;
26
+ authProvider?: AuthProvider;
24
27
  isPinned: boolean;
25
28
  /** All executions for the current session, oldest-first */
26
29
  executions?: PlaygroundExecution[];
@@ -38,6 +41,7 @@
38
41
  pipelineId,
39
42
  workflow,
40
43
  endpointConfig,
44
+ authProvider,
41
45
  isPinned,
42
46
  executions = [],
43
47
  latestExecutionId = null,
@@ -46,10 +50,16 @@
46
50
  extraViews = []
47
51
  }: Props = $props();
48
52
 
53
+ const fd = getInstance();
54
+
55
+ // The default instance keeps the legacy bare key; additional instances get
56
+ // a scoped key so two editors' view-mode choices don't overwrite each other.
57
+ const viewModeKey = fd.isDefault ? VIEW_MODE_KEY_BASE : `${VIEW_MODE_KEY_BASE}:${fd.id}`;
58
+
49
59
  let viewMode = $state<ViewMode>('graph');
50
60
 
51
61
  onMount(() => {
52
- const stored = localStorage.getItem(VIEW_MODE_KEY);
62
+ const stored = localStorage.getItem(viewModeKey);
53
63
  if (!stored) return;
54
64
  const validKeys = [...BUILTIN_VIEWS, ...extraViews.map((v) => v.key)];
55
65
  if (validKeys.includes(stored)) viewMode = stored;
@@ -58,7 +68,7 @@
58
68
  function selectViewMode(mode: ViewMode) {
59
69
  viewMode = mode;
60
70
  try {
61
- localStorage.setItem(VIEW_MODE_KEY, mode);
71
+ localStorage.setItem(viewModeKey, mode);
62
72
  } catch (e) {
63
73
  logger.warn('[FlowDrop] Could not persist view mode to localStorage:', e);
64
74
  }
@@ -241,14 +251,27 @@
241
251
  {#if pipelineId}
242
252
  {#key pipelineId}
243
253
  {#if viewMode === 'kanban'}
244
- <PipelineKanbanView {pipelineId} {workflow} {endpointConfig} {refreshTrigger} />
254
+ <PipelineKanbanView
255
+ {pipelineId}
256
+ {workflow}
257
+ {endpointConfig}
258
+ {authProvider}
259
+ {refreshTrigger}
260
+ />
245
261
  {:else if viewMode === 'table'}
246
- <PipelineTableView {pipelineId} {workflow} {endpointConfig} {refreshTrigger} />
262
+ <PipelineTableView
263
+ {pipelineId}
264
+ {workflow}
265
+ {endpointConfig}
266
+ {authProvider}
267
+ {refreshTrigger}
268
+ />
247
269
  {:else if viewMode === 'graph'}
248
270
  <PipelineStatus
249
271
  {pipelineId}
250
272
  {workflow}
251
273
  {endpointConfig}
274
+ {authProvider}
252
275
  runLabel={pipelineId}
253
276
  {refreshTrigger}
254
277
  isEmbedded={true}
@@ -257,7 +280,7 @@
257
280
  {@const activeView = extraViews.find((v) => v.key === viewMode)}
258
281
  {#if activeView}
259
282
  {@const View = activeView.component}
260
- <View {pipelineId} {workflow} {endpointConfig} {refreshTrigger} />
283
+ <View {pipelineId} {workflow} {endpointConfig} {authProvider} {refreshTrigger} />
261
284
  {/if}
262
285
  {/if}
263
286
  {/key}
@@ -268,9 +291,9 @@
268
291
  width="100%"
269
292
  showNavbar={false}
270
293
  disableSidebar={true}
271
- lockWorkflow={true}
272
- readOnly={true}
294
+ mode="locked"
273
295
  {endpointConfig}
296
+ {authProvider}
274
297
  />
275
298
  {/if}
276
299
  </div>
@@ -1,10 +1,12 @@
1
1
  import type { Workflow, PipelineViewDef } from '../../types/index.js';
2
2
  import type { EndpointConfig } from '../../config/endpoints.js';
3
+ import type { AuthProvider } from '../../types/auth.js';
3
4
  import type { PlaygroundExecution } from '../../types/playground.js';
4
5
  interface Props {
5
6
  pipelineId: string | null;
6
7
  workflow: Workflow;
7
8
  endpointConfig: EndpointConfig;
9
+ authProvider?: AuthProvider;
8
10
  isPinned: boolean;
9
11
  /** All executions for the current session, oldest-first */
10
12
  executions?: PlaygroundExecution[];
@@ -25,19 +25,26 @@
25
25
  idle: 'mdi:circle-outline'
26
26
  };
27
27
 
28
- function formatDuration(ms: number | null | undefined): string | null {
29
- if (ms == null) return null;
30
- if (ms < 1000) return `${ms}ms`;
31
- if (ms < 60000) return `${(ms / 1000).toFixed(2)}s`;
32
- const mins = Math.floor(ms / 60000);
33
- const secs = Math.floor((ms % 60000) / 1000);
34
- return `${mins}m ${secs}s`;
35
- }
36
-
37
28
  function formatDateTime(iso: string | null | undefined): string | null {
38
29
  if (!iso) return null;
39
30
  return new Date(iso).toLocaleString();
40
31
  }
32
+
33
+ function formatJson(value: unknown): string {
34
+ if (typeof value === 'string') return value;
35
+ try {
36
+ return JSON.stringify(value, null, 2);
37
+ } catch {
38
+ return String(value);
39
+ }
40
+ }
41
+
42
+ /** Treat null/undefined and empty objects/arrays as "nothing to show". */
43
+ function hasData(value: unknown): boolean {
44
+ if (value == null) return false;
45
+ if (typeof value === 'object') return Object.keys(value).length > 0;
46
+ return value !== '';
47
+ }
41
48
  </script>
42
49
 
43
50
  <script lang="ts">
@@ -45,27 +52,42 @@
45
52
  import Icon from '@iconify/svelte';
46
53
  import { createPipelineDataFetcher, resolveStatus } from './pipelineViewUtils.svelte.js';
47
54
  import { getStatusTextColor } from '../../utils/nodeStatus.js';
48
- import type { NodeStatusData } from './pipelineViewUtils.svelte.js';
55
+ import { formatMicroseconds } from '../../utils/duration.js';
49
56
  import type { Workflow, WorkflowNode } from '../../types/index.js';
50
57
  import type { EndpointConfig } from '../../config/endpoints.js';
58
+ import type { AuthProvider } from '../../types/auth.js';
51
59
 
52
60
  interface Props {
53
61
  pipelineId: string;
54
62
  workflow: Workflow;
55
63
  endpointConfig: EndpointConfig;
64
+ authProvider?: AuthProvider;
56
65
  refreshTrigger?: number;
57
66
  }
58
67
 
59
- let { pipelineId, workflow, endpointConfig, refreshTrigger = 0 }: Props = $props();
68
+ let { pipelineId, workflow, endpointConfig, authProvider, refreshTrigger = 0 }: Props = $props();
60
69
 
61
- interface NodeRow {
62
- node: WorkflowNode;
70
+ interface JobRow {
71
+ /** Stable key: job id, or node id for nodes without a job yet */
72
+ key: string;
73
+ label: string;
74
+ typeId: string;
75
+ nodeId: string;
63
76
  status: NodeStatus;
64
- statusData: NodeStatusData | undefined;
77
+ started?: string | null;
78
+ completed?: string | null;
79
+ /** Duration in microseconds */
80
+ executionTimeUs?: number | null;
81
+ error?: string | null;
82
+ retryCount?: number | null;
83
+ maxRetries?: number | null;
84
+ inputData?: unknown;
85
+ outputData?: unknown;
65
86
  }
66
87
 
67
- // svelte-ignore state_referenced_locally — endpointConfig is consumed once to build the API client; it must be stable
68
- const fetcher = createPipelineDataFetcher(() => pipelineId, endpointConfig);
88
+ // endpointConfig is consumed once to build the API client; it must be stable
89
+ // svelte-ignore state_referenced_locally
90
+ const fetcher = createPipelineDataFetcher(() => pipelineId, endpointConfig, authProvider);
69
91
 
70
92
  $effect(() => {
71
93
  if (refreshTrigger <= 0) return;
@@ -73,28 +95,76 @@
73
95
  return () => clearTimeout(timer);
74
96
  });
75
97
 
76
- const sortedRows = $derived.by((): NodeRow[] =>
77
- workflow.nodes
98
+ // One row per job, timeline style: loop iterations create multiple jobs
99
+ // for the same node and each shows as its own row (label carries the #N
100
+ // suffix). Executed jobs sort by start time; never-started jobs keep
101
+ // pipeline order at the end, followed by nodes that have no job yet.
102
+ const sortedRows = $derived.by((): JobRow[] => {
103
+ const nodesById = new Map<string, WorkflowNode>(workflow.nodes.map((node) => [node.id, node]));
104
+
105
+ const jobRows: JobRow[] = [];
106
+ const nodesWithJobs = new Set<string>();
107
+ for (const job of fetcher.jobs) {
108
+ const node = nodesById.get(job.nodeId);
109
+ if (!node) continue;
110
+ nodesWithJobs.add(job.nodeId);
111
+ jobRows.push({
112
+ key: job.id,
113
+ label: job.label || node.data.label,
114
+ typeId: node.data.metadata.id,
115
+ nodeId: job.nodeId,
116
+ status: resolveStatus({ status: job.status }),
117
+ started: job.started,
118
+ completed: job.completed,
119
+ executionTimeUs: job.executionTimeUs,
120
+ error: job.error,
121
+ retryCount: job.retryCount,
122
+ maxRetries: job.maxRetries,
123
+ inputData: job.inputData,
124
+ outputData: job.outputData
125
+ });
126
+ }
127
+
128
+ const startedRows = jobRows
129
+ .filter((row) => row.started)
130
+ .sort((a, b) => Date.parse(a.started!) - Date.parse(b.started!));
131
+ const neverStartedRows = jobRows.filter((row) => !row.started);
132
+
133
+ const nodeRows: JobRow[] = workflow.nodes
134
+ .filter((node) => !nodesWithJobs.has(node.id))
78
135
  .map((node) => {
79
136
  const statusData = fetcher.nodeStatusMap[node.id];
80
- return { node, status: resolveStatus(statusData), statusData };
137
+ return {
138
+ key: node.id,
139
+ label: node.data.label,
140
+ typeId: node.data.metadata.id,
141
+ nodeId: node.id,
142
+ status: resolveStatus(statusData),
143
+ started: statusData?.last_executed,
144
+ executionTimeUs:
145
+ statusData?.execution_time_us ??
146
+ (statusData?.execution_time != null ? statusData.execution_time * 1000 : null),
147
+ error: statusData?.error
148
+ };
81
149
  })
82
- .sort((a, b) => (STATUS_ORDER[a.status] ?? Infinity) - (STATUS_ORDER[b.status] ?? Infinity))
83
- );
150
+ .sort((a, b) => (STATUS_ORDER[a.status] ?? Infinity) - (STATUS_ORDER[b.status] ?? Infinity));
151
+
152
+ return [...startedRows, ...neverStartedRows, ...nodeRows];
153
+ });
84
154
 
85
155
  let expandedIds = $state(new Set<string>());
86
156
 
87
- function hasDetails(row: NodeRow): boolean {
88
- return !!(row.statusData?.last_executed || row.statusData?.error);
157
+ function hasDetails(row: JobRow): boolean {
158
+ return !!(row.started || row.error || hasData(row.inputData) || hasData(row.outputData));
89
159
  }
90
160
 
91
- function toggleRow(row: NodeRow) {
161
+ function toggleRow(row: JobRow) {
92
162
  if (!hasDetails(row)) return;
93
163
  const next = new Set(expandedIds);
94
- if (next.has(row.node.id)) {
95
- next.delete(row.node.id);
164
+ if (next.has(row.key)) {
165
+ next.delete(row.key);
96
166
  } else {
97
- next.add(row.node.id);
167
+ next.add(row.key);
98
168
  }
99
169
  expandedIds = next;
100
170
  }
@@ -121,12 +191,13 @@
121
191
  <th class="pipeline-table__th">Node</th>
122
192
  <th class="pipeline-table__th">Type</th>
123
193
  <th class="pipeline-table__th">Status</th>
194
+ <th class="pipeline-table__th pipeline-table__th--duration">Duration</th>
124
195
  <th class="pipeline-table__th pipeline-table__th--id">ID</th>
125
196
  </tr>
126
197
  </thead>
127
198
  <tbody>
128
- {#each sortedRows as row (row.node.id)}
129
- {@const expanded = expandedIds.has(row.node.id)}
199
+ {#each sortedRows as row (row.key)}
200
+ {@const expanded = expandedIds.has(row.key)}
130
201
  {@const expandable = hasDetails(row)}
131
202
  <tr
132
203
  class="pipeline-table__row"
@@ -144,12 +215,11 @@
144
215
  />
145
216
  {/if}
146
217
  </td>
147
- <td class="pipeline-table__td pipeline-table__td--label" title={row.node.data.label}
148
- >{row.node.data.label}</td
218
+ <td class="pipeline-table__td pipeline-table__td--label" title={row.label}
219
+ >{row.label}</td
149
220
  >
150
- <td
151
- class="pipeline-table__td pipeline-table__td--muted"
152
- title={row.node.data.metadata.id}>{row.node.data.metadata.id}</td
221
+ <td class="pipeline-table__td pipeline-table__td--muted" title={row.typeId}
222
+ >{row.typeId}</td
153
223
  >
154
224
  <td class="pipeline-table__td">
155
225
  <span
@@ -163,33 +233,62 @@
163
233
  {row.status}
164
234
  </span>
165
235
  </td>
166
- <td class="pipeline-table__td pipeline-table__td--id" title={row.node.id}
167
- >{row.node.id}</td
236
+ <td class="pipeline-table__td pipeline-table__td--duration">
237
+ {formatMicroseconds(row.executionTimeUs) ?? '—'}
238
+ </td>
239
+ <td class="pipeline-table__td pipeline-table__td--id" title={row.nodeId}
240
+ >{row.nodeId}</td
168
241
  >
169
242
  </tr>
170
243
  {#if expanded && expandable}
171
244
  <tr class="pipeline-table__detail-row">
172
- <td colspan="5" class="pipeline-table__detail-cell">
245
+ <td colspan="6" class="pipeline-table__detail-cell">
173
246
  <dl class="pipeline-table__details">
174
- {#if row.statusData?.last_executed}
247
+ {#if row.started}
248
+ <div class="pipeline-table__detail-item">
249
+ <dt>Started</dt>
250
+ <dd>{formatDateTime(row.started)}</dd>
251
+ </div>
252
+ {/if}
253
+ {#if row.completed}
175
254
  <div class="pipeline-table__detail-item">
176
- <dt>Last executed</dt>
177
- <dd>{formatDateTime(row.statusData.last_executed)}</dd>
255
+ <dt>Completed</dt>
256
+ <dd>{formatDateTime(row.completed)}</dd>
178
257
  </div>
179
258
  {/if}
180
- {#if row.statusData?.execution_time != null}
259
+ {#if row.executionTimeUs != null}
181
260
  <div class="pipeline-table__detail-item">
182
261
  <dt>Duration</dt>
183
- <dd>{formatDuration(row.statusData.execution_time)}</dd>
262
+ <dd>{formatMicroseconds(row.executionTimeUs)}</dd>
184
263
  </div>
185
264
  {/if}
186
- {#if row.statusData?.error}
265
+ {#if row.retryCount != null && row.retryCount > 0}
266
+ <div class="pipeline-table__detail-item">
267
+ <dt>Retries</dt>
268
+ <dd>
269
+ {row.retryCount}{row.maxRetries != null ? ` / ${row.maxRetries}` : ''}
270
+ </dd>
271
+ </div>
272
+ {/if}
273
+ {#if row.error}
187
274
  <div class="pipeline-table__detail-item pipeline-table__detail-item--error">
188
275
  <dt>Error</dt>
189
- <dd>{row.statusData.error}</dd>
276
+ <dd>{row.error}</dd>
190
277
  </div>
191
278
  {/if}
192
279
  </dl>
280
+ {#if hasData(row.inputData)}
281
+ <details class="pipeline-table__data">
282
+ <summary class="pipeline-table__data-summary">Input data</summary>
283
+ <pre class="pipeline-table__data-pre">{formatJson(row.inputData)}</pre>
284
+ </details>
285
+ {/if}
286
+ {#if hasData(row.outputData)}
287
+ <details class="pipeline-table__data">
288
+ <summary class="pipeline-table__data-summary">Output data</summary>
289
+ <pre class="pipeline-table__data-pre">{formatJson(row.outputData)}</pre>
290
+ </details>
291
+ {/if}
193
292
  </td>
194
293
  </tr>
195
294
  {/if}
@@ -270,6 +369,19 @@
270
369
  font-family: var(--fd-font-mono, monospace);
271
370
  }
272
371
 
372
+ .pipeline-table__th--duration,
373
+ .pipeline-table__td--duration {
374
+ text-align: right;
375
+ white-space: nowrap;
376
+ font-variant-numeric: tabular-nums;
377
+ }
378
+
379
+ .pipeline-table__td--duration {
380
+ font-family: var(--fd-font-mono, monospace);
381
+ font-size: var(--fd-text-2xs);
382
+ color: var(--fd-muted-foreground);
383
+ }
384
+
273
385
  .pipeline-table__th--expand {
274
386
  width: 1.5rem;
275
387
  padding-right: 0;
@@ -375,6 +487,38 @@
375
487
  font-size: var(--fd-text-2xs);
376
488
  }
377
489
 
490
+ .pipeline-table__data {
491
+ margin-top: var(--fd-space-sm);
492
+ }
493
+
494
+ .pipeline-table__data-summary {
495
+ cursor: pointer;
496
+ font-size: var(--fd-text-2xs);
497
+ font-weight: 600;
498
+ text-transform: uppercase;
499
+ letter-spacing: 0.05em;
500
+ color: var(--fd-muted-foreground);
501
+ user-select: none;
502
+ }
503
+
504
+ .pipeline-table__data-summary:hover {
505
+ color: var(--fd-foreground);
506
+ }
507
+
508
+ .pipeline-table__data-pre {
509
+ margin: var(--fd-space-2xs) 0 0;
510
+ padding: var(--fd-space-sm);
511
+ max-height: 16rem;
512
+ overflow: auto;
513
+ font-family: var(--fd-font-mono, monospace);
514
+ font-size: var(--fd-text-2xs);
515
+ background-color: var(--fd-muted);
516
+ border: 1px solid var(--fd-border);
517
+ border-radius: var(--fd-radius-sm, 4px);
518
+ white-space: pre-wrap;
519
+ word-break: break-word;
520
+ }
521
+
378
522
  .pipeline-table__status {
379
523
  display: inline-flex;
380
524
  align-items: center;
@@ -1,9 +1,11 @@
1
1
  import type { Workflow } from '../../types/index.js';
2
2
  import type { EndpointConfig } from '../../config/endpoints.js';
3
+ import type { AuthProvider } from '../../types/auth.js';
3
4
  interface Props {
4
5
  pipelineId: string;
5
6
  workflow: Workflow;
6
7
  endpointConfig: EndpointConfig;
8
+ authProvider?: AuthProvider;
7
9
  refreshTrigger?: number;
8
10
  }
9
11
  declare const PipelineTableView: import("svelte").Component<Props, {}, "">;