@flowdrop/flowdrop 1.8.0 → 1.9.0

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.
@@ -0,0 +1,372 @@
1
+ <script lang="ts">
2
+ import PipelineStatus from '../PipelineStatus.svelte';
3
+ import Icon from '@iconify/svelte';
4
+ import type { Workflow } from '../../types/index.js';
5
+ import type { EndpointConfig } from '../../config/endpoints.js';
6
+ import type { PlaygroundExecution } from '../../types/playground.js';
7
+
8
+ interface Props {
9
+ pipelineId: string | null;
10
+ workflow: Workflow;
11
+ endpointConfig: EndpointConfig;
12
+ isPinned: boolean;
13
+ runLabel?: string;
14
+ /** All executions for the current session, oldest-first */
15
+ executions?: PlaygroundExecution[];
16
+ /** ID of the most-recent execution */
17
+ latestExecutionId?: string | null;
18
+ /** Called with an execution ID to pin it, or null to follow latest */
19
+ onSelectExecution?: (id: string | null) => void;
20
+ }
21
+
22
+ let {
23
+ pipelineId,
24
+ workflow,
25
+ endpointConfig,
26
+ isPinned,
27
+ runLabel,
28
+ executions = [],
29
+ latestExecutionId = null,
30
+ onSelectExecution,
31
+ }: Props = $props();
32
+
33
+ let runDropdownOpen = $state(false);
34
+ let chipWrapEl = $state<HTMLElement | null>(null);
35
+
36
+ // Close run popover on outside click
37
+ $effect(() => {
38
+ if (!runDropdownOpen) return;
39
+ function handleOutside(e: MouseEvent) {
40
+ if (!chipWrapEl?.contains(e.target as Node)) {
41
+ runDropdownOpen = false;
42
+ }
43
+ }
44
+ document.addEventListener('click', handleOutside);
45
+ return () => document.removeEventListener('click', handleOutside);
46
+ });
47
+
48
+ function statusIcon(status: PlaygroundExecution['status']): string {
49
+ if (status === 'running') return 'mdi:loading';
50
+ if (status === 'failed') return 'mdi:alert-circle';
51
+ return 'mdi:check-circle';
52
+ }
53
+
54
+ function statusClass(status: PlaygroundExecution['status']): string {
55
+ if (status === 'running') return 'pipeline-panel__run-status--running';
56
+ if (status === 'failed') return 'pipeline-panel__run-status--failed';
57
+ return 'pipeline-panel__run-status--completed';
58
+ }
59
+ </script>
60
+
61
+ <div class="pipeline-panel">
62
+ <div class="pipeline-panel__header">
63
+ <Icon icon="mdi:source-branch" class="pipeline-panel__icon" />
64
+ <span class="pipeline-panel__title">Pipeline</span>
65
+
66
+ {#if pipelineId && executions.length > 0}
67
+ <!-- Run picker chip -->
68
+ <div class="pipeline-panel__run-chip-wrap" bind:this={chipWrapEl}>
69
+ <button
70
+ type="button"
71
+ class="pipeline-panel__run-chip"
72
+ class:pipeline-panel__run-chip--pinned={isPinned}
73
+ class:pipeline-panel__run-chip--open={runDropdownOpen}
74
+ onclick={() => (runDropdownOpen = !runDropdownOpen)}
75
+ title="Switch run"
76
+ >
77
+ <span class="pipeline-panel__run-chip-label">{runLabel ?? 'Run'}</span>
78
+ <Icon icon={runDropdownOpen ? 'mdi:chevron-up' : 'mdi:chevron-down'} class="pipeline-panel__run-chip-chevron" />
79
+ </button>
80
+
81
+ {#if runDropdownOpen}
82
+ <div class="pipeline-panel__run-popover">
83
+ {#each [...executions].reverse() as exec, i (exec.id)}
84
+ {@const index = executions.length - i}
85
+ {@const isActive = pipelineId === exec.id}
86
+ <button
87
+ type="button"
88
+ class="pipeline-panel__run-popover-item"
89
+ class:pipeline-panel__run-popover-item--active={isActive}
90
+ onclick={() => {
91
+ onSelectExecution?.(exec.id === latestExecutionId ? null : exec.id);
92
+ runDropdownOpen = false;
93
+ }}
94
+ >
95
+ <Icon
96
+ icon={statusIcon(exec.status)}
97
+ class="pipeline-panel__run-status {statusClass(exec.status)}"
98
+ />
99
+ <span>Run #{index}</span>
100
+ {#if isActive}
101
+ <Icon icon="mdi:check" class="pipeline-panel__run-popover-check" />
102
+ {/if}
103
+ </button>
104
+ {/each}
105
+ </div>
106
+ {/if}
107
+ </div>
108
+
109
+ <!-- Latest toggle -->
110
+ <button
111
+ type="button"
112
+ class="pipeline-panel__latest-toggle"
113
+ class:pipeline-panel__latest-toggle--active={!isPinned}
114
+ onclick={() => {
115
+ if (isPinned) {
116
+ onSelectExecution?.(null);
117
+ } else {
118
+ onSelectExecution?.(latestExecutionId);
119
+ }
120
+ }}
121
+ title={isPinned ? 'Following latest is off — click to resume' : 'Always showing the most recent run'}
122
+ >
123
+ <Icon icon="mdi:refresh" />
124
+ Latest
125
+ </button>
126
+ {:else if pipelineId}
127
+ <span
128
+ class="pipeline-panel__status-badge pipeline-panel__status-badge--live"
129
+ title="Showing the most recent execution"
130
+ >Latest</span>
131
+ {/if}
132
+ </div>
133
+
134
+ {#if pipelineId}
135
+ {#key pipelineId}
136
+ <div class="pipeline-panel__content">
137
+ <PipelineStatus {pipelineId} {workflow} {endpointConfig} {runLabel} isEmbedded={true} />
138
+ </div>
139
+ {/key}
140
+ {:else}
141
+ <div class="pipeline-panel__empty">
142
+ <Icon icon="mdi:source-branch" class="pipeline-panel__empty-icon" />
143
+ <p class="pipeline-panel__empty-text">Run the workflow to see the pipeline.</p>
144
+ </div>
145
+ {/if}
146
+ </div>
147
+
148
+ <style>
149
+ .pipeline-panel {
150
+ display: flex;
151
+ flex-direction: column;
152
+ height: 100%;
153
+ min-height: 0;
154
+ overflow: hidden;
155
+ background-color: var(--fd-muted);
156
+ }
157
+
158
+ .pipeline-panel__header {
159
+ display: flex;
160
+ align-items: center;
161
+ gap: var(--fd-space-xs);
162
+ padding: 0 var(--fd-space-xl);
163
+ height: var(--fd-playground-header-height);
164
+ min-height: var(--fd-playground-header-height);
165
+ border-bottom: 1px solid var(--fd-border);
166
+ flex-shrink: 0;
167
+ }
168
+
169
+ :global(.pipeline-panel__icon) {
170
+ font-size: var(--fd-text-base);
171
+ color: var(--fd-muted-foreground);
172
+ flex-shrink: 0;
173
+ }
174
+
175
+ .pipeline-panel__title {
176
+ font-size: var(--fd-text-sm);
177
+ font-weight: 600;
178
+ color: var(--fd-foreground);
179
+ flex: 1;
180
+ }
181
+
182
+ .pipeline-panel__status-badge {
183
+ font-size: var(--fd-text-2xs);
184
+ font-weight: 600;
185
+ text-transform: uppercase;
186
+ letter-spacing: 0.05em;
187
+ padding: 2px var(--fd-space-xs);
188
+ border-radius: var(--fd-radius-sm);
189
+ flex-shrink: 0;
190
+ }
191
+
192
+ .pipeline-panel__status-badge--live {
193
+ background-color: var(--fd-success-muted);
194
+ color: var(--fd-success);
195
+ }
196
+
197
+ /* Run picker chip */
198
+ .pipeline-panel__run-chip-wrap {
199
+ position: relative;
200
+ flex-shrink: 0;
201
+ }
202
+
203
+ .pipeline-panel__run-chip {
204
+ display: inline-flex;
205
+ align-items: center;
206
+ gap: var(--fd-space-3xs);
207
+ padding: 2px var(--fd-space-xs) 2px var(--fd-space-sm);
208
+ border: 1px solid var(--fd-border);
209
+ border-radius: var(--fd-radius-md);
210
+ background: var(--fd-background);
211
+ color: var(--fd-foreground);
212
+ font-size: var(--fd-text-xs);
213
+ font-weight: 500;
214
+ cursor: pointer;
215
+ transition: all var(--fd-transition-fast);
216
+ line-height: 1;
217
+ }
218
+
219
+ .pipeline-panel__run-chip:hover,
220
+ .pipeline-panel__run-chip--open {
221
+ background-color: var(--fd-muted);
222
+ border-color: var(--fd-border-strong);
223
+ }
224
+
225
+ .pipeline-panel__run-chip--pinned {
226
+ background-color: var(--fd-primary-muted);
227
+ border-color: transparent;
228
+ color: var(--fd-primary);
229
+ }
230
+
231
+ .pipeline-panel__run-chip--pinned:hover,
232
+ .pipeline-panel__run-chip--pinned.pipeline-panel__run-chip--open {
233
+ border-color: var(--fd-primary);
234
+ }
235
+
236
+ :global(.pipeline-panel__run-chip-chevron) {
237
+ color: var(--fd-muted-foreground);
238
+ font-size: var(--fd-text-xs);
239
+ flex-shrink: 0;
240
+ }
241
+
242
+ /* Run popover */
243
+ .pipeline-panel__run-popover {
244
+ position: absolute;
245
+ top: calc(100% + var(--fd-space-xs));
246
+ right: 0;
247
+ z-index: 50;
248
+ min-width: 160px;
249
+ padding: var(--fd-space-xs);
250
+ background-color: var(--fd-background);
251
+ border: 1px solid var(--fd-border);
252
+ border-radius: var(--fd-radius-lg);
253
+ box-shadow: var(--fd-shadow-lg);
254
+ }
255
+
256
+ .pipeline-panel__run-popover-item {
257
+ display: flex;
258
+ align-items: center;
259
+ gap: var(--fd-space-sm);
260
+ width: 100%;
261
+ padding: var(--fd-space-sm) var(--fd-space-sm);
262
+ border: none;
263
+ border-radius: var(--fd-radius-sm);
264
+ background: transparent;
265
+ color: var(--fd-foreground);
266
+ font-size: var(--fd-text-sm);
267
+ text-align: left;
268
+ cursor: pointer;
269
+ transition: background-color var(--fd-transition-fast);
270
+ }
271
+
272
+ .pipeline-panel__run-popover-item:hover {
273
+ background-color: var(--fd-muted);
274
+ }
275
+
276
+ .pipeline-panel__run-popover-item--active {
277
+ font-weight: 500;
278
+ }
279
+
280
+ :global(.pipeline-panel__run-popover-check) {
281
+ color: var(--fd-primary) !important;
282
+ margin-left: auto;
283
+ font-size: var(--fd-text-sm);
284
+ }
285
+
286
+ :global(.pipeline-panel__run-status) {
287
+ flex-shrink: 0;
288
+ font-size: var(--fd-text-sm);
289
+ }
290
+
291
+ :global(.pipeline-panel__run-status--running) {
292
+ color: var(--fd-warning);
293
+ animation: pp-spin 1s linear infinite;
294
+ }
295
+
296
+ :global(.pipeline-panel__run-status--completed) {
297
+ color: var(--fd-success);
298
+ }
299
+
300
+ :global(.pipeline-panel__run-status--failed) {
301
+ color: var(--fd-error);
302
+ }
303
+
304
+ @keyframes pp-spin {
305
+ from { transform: rotate(0deg); }
306
+ to { transform: rotate(360deg); }
307
+ }
308
+
309
+ /* Latest toggle */
310
+ .pipeline-panel__latest-toggle {
311
+ display: inline-flex;
312
+ align-items: center;
313
+ gap: var(--fd-space-3xs);
314
+ padding: var(--fd-space-3xs) var(--fd-space-sm);
315
+ border: 1px solid var(--fd-border);
316
+ border-radius: var(--fd-radius-md);
317
+ background: transparent;
318
+ color: var(--fd-muted-foreground);
319
+ font-size: var(--fd-text-xs);
320
+ font-weight: 500;
321
+ cursor: pointer;
322
+ transition: all var(--fd-transition-fast);
323
+ line-height: 1;
324
+ flex-shrink: 0;
325
+ }
326
+
327
+ .pipeline-panel__latest-toggle :global(svg) {
328
+ font-size: var(--fd-text-xs);
329
+ }
330
+
331
+ .pipeline-panel__latest-toggle:hover {
332
+ background-color: var(--fd-muted);
333
+ color: var(--fd-foreground);
334
+ border-color: var(--fd-border-strong);
335
+ }
336
+
337
+ .pipeline-panel__latest-toggle--active {
338
+ background-color: var(--fd-primary-muted);
339
+ border-color: var(--fd-primary);
340
+ color: var(--fd-primary);
341
+ }
342
+
343
+ .pipeline-panel__content {
344
+ flex: 1;
345
+ min-height: 0;
346
+ overflow: hidden;
347
+ }
348
+
349
+ .pipeline-panel__empty {
350
+ flex: 1;
351
+ display: flex;
352
+ flex-direction: column;
353
+ align-items: center;
354
+ justify-content: center;
355
+ gap: var(--fd-space-md);
356
+ color: var(--fd-muted-foreground);
357
+ padding: var(--fd-space-4xl);
358
+ text-align: center;
359
+ }
360
+
361
+ :global(.pipeline-panel__empty-icon) {
362
+ font-size: var(--fd-space-6xl);
363
+ opacity: 0.4;
364
+ }
365
+
366
+ .pipeline-panel__empty-text {
367
+ font-size: var(--fd-text-sm);
368
+ margin: 0;
369
+ max-width: 200px;
370
+ line-height: var(--fd-leading-relaxed);
371
+ }
372
+ </style>
@@ -0,0 +1,19 @@
1
+ import type { Workflow } from '../../types/index.js';
2
+ import type { EndpointConfig } from '../../config/endpoints.js';
3
+ import type { PlaygroundExecution } from '../../types/playground.js';
4
+ interface Props {
5
+ pipelineId: string | null;
6
+ workflow: Workflow;
7
+ endpointConfig: EndpointConfig;
8
+ isPinned: boolean;
9
+ runLabel?: string;
10
+ /** All executions for the current session, oldest-first */
11
+ executions?: PlaygroundExecution[];
12
+ /** ID of the most-recent execution */
13
+ latestExecutionId?: string | null;
14
+ /** Called with an execution ID to pin it, or null to follow latest */
15
+ onSelectExecution?: (id: string | null) => void;
16
+ }
17
+ declare const PipelinePanel: import("svelte").Component<Props, {}, "">;
18
+ type PipelinePanel = ReturnType<typeof PipelinePanel>;
19
+ export default PipelinePanel;