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