@flowdrop/flowdrop 1.12.0 → 1.14.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.
- package/README.md +5 -0
- package/dist/components/ConfigForm.svelte +1 -0
- package/dist/components/ConfigPanel.svelte +7 -1
- package/dist/components/NodeSwapPicker.svelte +5 -1
- package/dist/components/PipelineStatus.svelte +11 -2
- package/dist/components/SchemaForm.svelte +1 -0
- package/dist/components/SettingsPanel.svelte +5 -1
- package/dist/components/WorkflowEditor.svelte +5 -1
- package/dist/components/chat/AIChatPanel.svelte +1 -5
- package/dist/components/form/FormAutocomplete.svelte +69 -15
- package/dist/components/form/FormField.svelte +21 -0
- package/dist/components/form/FormFieldLight.svelte +1 -0
- package/dist/components/interrupt/ChoicePrompt.svelte +5 -1
- package/dist/components/interrupt/InterruptBubble.svelte +75 -17
- package/dist/components/interrupt/InterruptBubble.svelte.d.ts +11 -0
- package/dist/components/playground/ChatBubble.svelte +287 -0
- package/dist/components/playground/ChatBubble.svelte.d.ts +10 -0
- package/dist/components/playground/ChatInput.svelte +11 -5
- package/dist/components/playground/ControlPanel.svelte +42 -29
- package/dist/components/playground/ExecutionConsole.svelte +5 -1
- package/dist/components/playground/ExecutionConsole.svelte.d.ts +2 -0
- package/dist/components/playground/ExecutionList.svelte +7 -2
- package/dist/components/playground/HierarchyTrail.svelte +88 -0
- package/dist/components/playground/HierarchyTrail.svelte.d.ts +7 -0
- package/dist/components/playground/LogRow.svelte +179 -0
- package/dist/components/playground/LogRow.svelte.d.ts +8 -0
- package/dist/components/playground/MessageBubble.stories.svelte +89 -0
- package/dist/components/playground/MessageBubble.svelte +23 -738
- package/dist/components/playground/MessageBubble.svelte.d.ts +3 -11
- package/dist/components/playground/MessageCard.svelte +107 -0
- package/dist/components/playground/MessageCard.svelte.d.ts +10 -0
- package/dist/components/playground/MessageMarkdown.svelte +170 -0
- package/dist/components/playground/MessageMarkdown.svelte.d.ts +7 -0
- package/dist/components/playground/MessageNotice.svelte +121 -0
- package/dist/components/playground/MessageNotice.svelte.d.ts +9 -0
- package/dist/components/playground/MessageStream.svelte +215 -10
- package/dist/components/playground/MessageStream.svelte.d.ts +5 -0
- package/dist/components/playground/MessageTagChip.svelte +117 -0
- package/dist/components/playground/MessageTagChip.svelte.d.ts +7 -0
- package/dist/components/playground/MessageTagStrip.svelte +37 -0
- package/dist/components/playground/MessageTagStrip.svelte.d.ts +7 -0
- package/dist/components/playground/PipelineKanbanView.svelte +40 -11
- package/dist/components/playground/PipelinePanel.svelte +5 -1
- package/dist/components/playground/PipelineTableView.svelte +20 -6
- package/dist/components/playground/Playground.svelte +84 -22
- package/dist/components/playground/PlaygroundStudio.svelte +99 -7
- package/dist/components/playground/messageDisplay.d.ts +19 -0
- package/dist/components/playground/messageDisplay.js +62 -0
- package/dist/components/playground/pipelineViewUtils.svelte.js +11 -4
- package/dist/form/autocomplete.d.ts +1 -0
- package/dist/form/autocomplete.js +1 -0
- package/dist/form/index.d.ts +17 -0
- package/dist/form/index.js +19 -0
- package/dist/messages/defaults.d.ts +5 -0
- package/dist/messages/defaults.js +6 -0
- package/dist/openapi/v1/openapi.yaml +6403 -0
- package/dist/schemas/v1/workflow.schema.json +46 -1
- package/dist/services/categoriesApi.d.ts +2 -1
- package/dist/services/categoriesApi.js +5 -3
- package/dist/services/playgroundService.d.ts +23 -4
- package/dist/services/playgroundService.js +22 -9
- package/dist/services/portConfigApi.d.ts +2 -1
- package/dist/services/portConfigApi.js +5 -3
- package/dist/stores/playgroundStore.svelte.d.ts +22 -1
- package/dist/stores/playgroundStore.svelte.js +109 -32
- package/dist/svelte-app.d.ts +1 -0
- package/dist/svelte-app.js +5 -5
- package/dist/types/index.d.ts +13 -0
- package/dist/types/playground.d.ts +112 -2
- package/dist/types/playground.js +14 -0
- package/package.json +12 -1
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
getChatMessages,
|
|
25
25
|
getIsExecuting,
|
|
26
26
|
getCurrentSession,
|
|
27
|
-
getShowLogs
|
|
27
|
+
getShowLogs,
|
|
28
|
+
getHasOlder
|
|
28
29
|
} from '../../stores/playgroundStore.svelte.js';
|
|
29
30
|
import {
|
|
30
31
|
getInterruptsMap,
|
|
@@ -51,6 +52,11 @@
|
|
|
51
52
|
compactSystemMessages?: boolean;
|
|
52
53
|
/** Called when an interrupt is resolved */
|
|
53
54
|
onInterruptResolved?: () => void;
|
|
55
|
+
/**
|
|
56
|
+
* Called when the user scrolls near the top, to load older messages.
|
|
57
|
+
* When omitted, scroll-up paging is disabled (e.g. view-only surfaces).
|
|
58
|
+
*/
|
|
59
|
+
onLoadOlder?: () => void | Promise<void>;
|
|
54
60
|
/** Custom render for the no-session welcome state */
|
|
55
61
|
welcome?: Snippet;
|
|
56
62
|
/** Custom render for the empty-session state */
|
|
@@ -64,6 +70,7 @@
|
|
|
64
70
|
allowLogs = false,
|
|
65
71
|
compactSystemMessages = true,
|
|
66
72
|
onInterruptResolved,
|
|
73
|
+
onLoadOlder,
|
|
67
74
|
welcome,
|
|
68
75
|
emptySession
|
|
69
76
|
}: Props = $props();
|
|
@@ -71,14 +78,14 @@
|
|
|
71
78
|
const states = $derived(m().playground.states);
|
|
72
79
|
|
|
73
80
|
/** Reference to the messages container for scrolling */
|
|
74
|
-
let messagesContainer
|
|
81
|
+
let messagesContainer = $state<HTMLDivElement | undefined>();
|
|
75
82
|
|
|
76
|
-
const displayMessages = $derived(
|
|
77
|
-
allowLogs && getShowLogs() ? getMessages() : getChatMessages()
|
|
78
|
-
);
|
|
83
|
+
const displayMessages = $derived(allowLogs && getShowLogs() ? getMessages() : getChatMessages());
|
|
79
84
|
|
|
80
85
|
let previousMessageCount = 0;
|
|
81
86
|
let userScrolledUp = false;
|
|
87
|
+
let isLoadingOlder = $state(false);
|
|
88
|
+
let topSentinel = $state<HTMLDivElement | undefined>();
|
|
82
89
|
|
|
83
90
|
function handleScroll() {
|
|
84
91
|
if (!messagesContainer) return;
|
|
@@ -86,6 +93,48 @@
|
|
|
86
93
|
userScrolledUp = scrollHeight - scrollTop - clientHeight > 50;
|
|
87
94
|
}
|
|
88
95
|
|
|
96
|
+
// Load older messages when the top sentinel scrolls into view. An observer is
|
|
97
|
+
// self-throttling and keeps layout reads off the scroll path; rootMargin
|
|
98
|
+
// pre-fetches the next page slightly before the user reaches the very top.
|
|
99
|
+
$effect(() => {
|
|
100
|
+
if (!topSentinel || !messagesContainer || !onLoadOlder) return;
|
|
101
|
+
const observer = new IntersectionObserver(
|
|
102
|
+
(entries) => {
|
|
103
|
+
if (entries[0]?.isIntersecting) void loadOlder();
|
|
104
|
+
},
|
|
105
|
+
{ root: messagesContainer, rootMargin: '300px 0px 0px 0px' }
|
|
106
|
+
);
|
|
107
|
+
observer.observe(topSentinel);
|
|
108
|
+
return () => observer.disconnect();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Fetch the previous page and keep the viewport pinned to the message the
|
|
113
|
+
* user is reading. We anchor on a real DOM node (the first rendered message)
|
|
114
|
+
* and compensate scrollTop by however far it moved — robust against the
|
|
115
|
+
* loading spinner (which is out of flow) and any late reflow above it.
|
|
116
|
+
* A scroll during the in-flight fetch is intentionally overridden so the
|
|
117
|
+
* prepend doesn't shift the reading position.
|
|
118
|
+
*/
|
|
119
|
+
async function loadOlder() {
|
|
120
|
+
if (!onLoadOlder || !messagesContainer || isLoadingOlder || !getHasOlder()) return;
|
|
121
|
+
|
|
122
|
+
const anchor = topSentinel?.nextElementSibling as HTMLElement | null;
|
|
123
|
+
const anchorTopBefore = anchor?.getBoundingClientRect().top ?? 0;
|
|
124
|
+
|
|
125
|
+
isLoadingOlder = true;
|
|
126
|
+
try {
|
|
127
|
+
await onLoadOlder();
|
|
128
|
+
await tick();
|
|
129
|
+
if (messagesContainer && anchor) {
|
|
130
|
+
const shift = anchor.getBoundingClientRect().top - anchorTopBefore;
|
|
131
|
+
messagesContainer.scrollTop += shift;
|
|
132
|
+
}
|
|
133
|
+
} finally {
|
|
134
|
+
isLoadingOlder = false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
89
138
|
function isFormFocused(): boolean {
|
|
90
139
|
if (!messagesContainer) return false;
|
|
91
140
|
const activeElement = document.activeElement;
|
|
@@ -154,14 +203,20 @@
|
|
|
154
203
|
const currentCount = displayMessages.length;
|
|
155
204
|
|
|
156
205
|
if (!autoScroll || !messagesContainer) {
|
|
157
|
-
untrack(() => {
|
|
206
|
+
untrack(() => {
|
|
207
|
+
previousMessageCount = currentCount;
|
|
208
|
+
});
|
|
158
209
|
return;
|
|
159
210
|
}
|
|
160
211
|
|
|
161
212
|
const hasNewMessage = currentCount > previousMessageCount;
|
|
162
|
-
untrack(() => {
|
|
213
|
+
untrack(() => {
|
|
214
|
+
previousMessageCount = currentCount;
|
|
215
|
+
});
|
|
163
216
|
|
|
164
|
-
|
|
217
|
+
// Don't chase the bottom while a backward page is landing — loadOlder owns
|
|
218
|
+
// scroll position during a prepend and anchors it to the message in view.
|
|
219
|
+
if (!hasNewMessage || userScrolledUp || isFormFocused() || isLoadingOlder) return;
|
|
165
220
|
|
|
166
221
|
tick().then(() => {
|
|
167
222
|
if (messagesContainer) {
|
|
@@ -171,7 +226,13 @@
|
|
|
171
226
|
});
|
|
172
227
|
</script>
|
|
173
228
|
|
|
174
|
-
<div
|
|
229
|
+
<div
|
|
230
|
+
class="message-stream"
|
|
231
|
+
role="log"
|
|
232
|
+
aria-label={m().playground.controlPanel.messageStreamLabel}
|
|
233
|
+
bind:this={messagesContainer}
|
|
234
|
+
onscroll={handleScroll}
|
|
235
|
+
>
|
|
175
236
|
{#if showWelcome}
|
|
176
237
|
{#if welcome}
|
|
177
238
|
{@render welcome()}
|
|
@@ -181,6 +242,12 @@
|
|
|
181
242
|
{@render emptySession()}
|
|
182
243
|
{/if}
|
|
183
244
|
{:else}
|
|
245
|
+
<div bind:this={topSentinel} class="message-stream__sentinel" aria-hidden="true"></div>
|
|
246
|
+
{#if isLoadingOlder}
|
|
247
|
+
<div class="message-stream__loading-older" aria-hidden="true">
|
|
248
|
+
<span class="message-stream__loading-older-spinner"></span>
|
|
249
|
+
</div>
|
|
250
|
+
{/if}
|
|
184
251
|
{#each displayMessages as message, index (message.id)}
|
|
185
252
|
{#if isInterruptMessage(message)}
|
|
186
253
|
{@const interrupt = getInterruptForMessage(message)}
|
|
@@ -189,6 +256,8 @@
|
|
|
189
256
|
{interrupt}
|
|
190
257
|
showTimestamp={showTimestamps}
|
|
191
258
|
onResolved={onInterruptResolved}
|
|
259
|
+
hierarchy={message.hierarchy}
|
|
260
|
+
tags={message.tags}
|
|
192
261
|
/>
|
|
193
262
|
{/if}
|
|
194
263
|
{:else}
|
|
@@ -217,10 +286,146 @@
|
|
|
217
286
|
|
|
218
287
|
<style>
|
|
219
288
|
.message-stream {
|
|
289
|
+
position: relative;
|
|
220
290
|
flex: 1;
|
|
221
291
|
min-height: 0;
|
|
222
292
|
overflow-y: auto;
|
|
223
293
|
padding: var(--fd-space-3xl);
|
|
294
|
+
|
|
295
|
+
/* Establish a containment context so message rows can adapt to the
|
|
296
|
+
stream's actual width (not the viewport's). The matching @container
|
|
297
|
+
queries (for .log-row) live below in the same <style> block, so
|
|
298
|
+
renaming the container only requires editing this file. */
|
|
299
|
+
container-type: inline-size;
|
|
300
|
+
container-name: fd-message-stream;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/* Zero-height marker the IntersectionObserver watches to trigger
|
|
304
|
+
backward pagination as it nears the top of the scroll area. */
|
|
305
|
+
.message-stream__sentinel {
|
|
306
|
+
height: 0;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/* Shared fade-in for newly-appended message rows. `-global-` so
|
|
310
|
+
ChatBubble.svelte / MessageCard.svelte can reference it without
|
|
311
|
+
redeclaring. Honour reduced-motion in the same place. */
|
|
312
|
+
@keyframes -global-fd-fade-in {
|
|
313
|
+
from {
|
|
314
|
+
opacity: 0;
|
|
315
|
+
transform: translateY(6px);
|
|
316
|
+
}
|
|
317
|
+
to {
|
|
318
|
+
opacity: 1;
|
|
319
|
+
transform: translateY(0);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
@media (prefers-reduced-motion: reduce) {
|
|
324
|
+
:global(.message-bubble),
|
|
325
|
+
:global(.message-card) {
|
|
326
|
+
animation: none;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/* Container-query reshaping for log rows. Lives next to the
|
|
331
|
+
container-name declaration so the coupling is local — selectors are
|
|
332
|
+
:global because .log-row is a sibling component's class.
|
|
333
|
+
|
|
334
|
+
Tier 1 (≤720px): two rows — level/body, then tags/timestamp.
|
|
335
|
+
Tier 2 (≤480px): collapse further; body forces internal line break. */
|
|
336
|
+
@container fd-message-stream (max-width: 720px) {
|
|
337
|
+
:global(.log-row) {
|
|
338
|
+
display: grid;
|
|
339
|
+
grid-template-columns: auto 1fr auto;
|
|
340
|
+
grid-template-areas:
|
|
341
|
+
'level body body'
|
|
342
|
+
'. tags timestamp';
|
|
343
|
+
align-items: baseline;
|
|
344
|
+
row-gap: var(--fd-space-2xs);
|
|
345
|
+
column-gap: var(--fd-space-sm);
|
|
346
|
+
}
|
|
347
|
+
:global(.log-row__level) {
|
|
348
|
+
grid-area: level;
|
|
349
|
+
}
|
|
350
|
+
:global(.log-row__body) {
|
|
351
|
+
grid-area: body;
|
|
352
|
+
min-width: 0;
|
|
353
|
+
}
|
|
354
|
+
:global(.log-row__tags) {
|
|
355
|
+
grid-area: tags;
|
|
356
|
+
justify-self: start;
|
|
357
|
+
}
|
|
358
|
+
:global(.log-row__timestamp) {
|
|
359
|
+
grid-area: timestamp;
|
|
360
|
+
justify-self: end;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
@container fd-message-stream (max-width: 480px) {
|
|
365
|
+
:global(.log-row) {
|
|
366
|
+
grid-template-columns: auto 1fr;
|
|
367
|
+
grid-template-areas:
|
|
368
|
+
'level body'
|
|
369
|
+
'. tags';
|
|
370
|
+
}
|
|
371
|
+
:global(.log-row__text) {
|
|
372
|
+
flex-basis: 100%;
|
|
373
|
+
min-width: 0;
|
|
374
|
+
}
|
|
375
|
+
:global(.log-row__timestamp) {
|
|
376
|
+
display: none;
|
|
377
|
+
}
|
|
378
|
+
/* Drop the source + node chips: source is implied by the level
|
|
379
|
+
colour, node duplicates the hierarchy trail's last entry. Keeping
|
|
380
|
+
them at this width forced each chip onto its own line and made
|
|
381
|
+
log rows 5–6 lines tall. */
|
|
382
|
+
:global(.log-row__source),
|
|
383
|
+
:global(.log-row__node) {
|
|
384
|
+
display: none;
|
|
385
|
+
}
|
|
386
|
+
/* Reclaim horizontal room by tightening the row's own padding —
|
|
387
|
+
can't shrink the stream's padding from inside its own
|
|
388
|
+
container query. */
|
|
389
|
+
:global(.log-row) {
|
|
390
|
+
padding-left: var(--fd-space-xs);
|
|
391
|
+
padding-right: var(--fd-space-xs);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/* Overlay, out of flow — its presence must not shift message layout, or it
|
|
396
|
+
would corrupt the scroll anchoring in loadOlder(). */
|
|
397
|
+
.message-stream__loading-older {
|
|
398
|
+
position: absolute;
|
|
399
|
+
top: 0;
|
|
400
|
+
left: 0;
|
|
401
|
+
right: 0;
|
|
402
|
+
display: flex;
|
|
403
|
+
align-items: center;
|
|
404
|
+
justify-content: center;
|
|
405
|
+
padding: var(--fd-space-sm) 0;
|
|
406
|
+
pointer-events: none;
|
|
407
|
+
z-index: 1;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.message-stream__loading-older-spinner {
|
|
411
|
+
width: var(--fd-space-lg);
|
|
412
|
+
height: var(--fd-space-lg);
|
|
413
|
+
border: 2px solid var(--fd-border-strong);
|
|
414
|
+
border-top-color: transparent;
|
|
415
|
+
border-radius: var(--fd-radius-full);
|
|
416
|
+
animation: message-stream-spin 0.8s linear infinite;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
@keyframes message-stream-spin {
|
|
420
|
+
to {
|
|
421
|
+
transform: rotate(360deg);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
@media (prefers-reduced-motion: reduce) {
|
|
426
|
+
.message-stream__loading-older-spinner {
|
|
427
|
+
animation: none;
|
|
428
|
+
}
|
|
224
429
|
}
|
|
225
430
|
|
|
226
431
|
.message-stream__typing {
|
|
@@ -277,7 +482,7 @@
|
|
|
277
482
|
|
|
278
483
|
@media (max-width: 640px) {
|
|
279
484
|
.message-stream {
|
|
280
|
-
padding: var(--fd-space-
|
|
485
|
+
padding: var(--fd-space-md) 0;
|
|
281
486
|
}
|
|
282
487
|
}
|
|
283
488
|
</style>
|
|
@@ -17,6 +17,11 @@ interface Props {
|
|
|
17
17
|
compactSystemMessages?: boolean;
|
|
18
18
|
/** Called when an interrupt is resolved */
|
|
19
19
|
onInterruptResolved?: () => void;
|
|
20
|
+
/**
|
|
21
|
+
* Called when the user scrolls near the top, to load older messages.
|
|
22
|
+
* When omitted, scroll-up paging is disabled (e.g. view-only surfaces).
|
|
23
|
+
*/
|
|
24
|
+
onLoadOlder?: () => void | Promise<void>;
|
|
20
25
|
/** Custom render for the no-session welcome state */
|
|
21
26
|
welcome?: Snippet;
|
|
22
27
|
/** Custom render for the empty-session state */
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
MessageTagChip Component
|
|
3
|
+
|
|
4
|
+
Renders a single server-emitted MessageTag as a compact chip. Semantic
|
|
5
|
+
color comes from tag.color, visual emphasis from tag.variant. Used by
|
|
6
|
+
MessageBubble and InterruptBubble.
|
|
7
|
+
|
|
8
|
+
Styling: a single base rule reads from CSS custom properties; one rule
|
|
9
|
+
per color sets --chip-c, one rule per variant sets bg/fg/border in terms
|
|
10
|
+
of --chip-c. Adding a color is one line.
|
|
11
|
+
-->
|
|
12
|
+
|
|
13
|
+
<script lang="ts">
|
|
14
|
+
import Icon from '@iconify/svelte';
|
|
15
|
+
import type { MessageTag } from '../../types/playground.js';
|
|
16
|
+
|
|
17
|
+
interface Props {
|
|
18
|
+
tag: MessageTag;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let { tag }: Props = $props();
|
|
22
|
+
|
|
23
|
+
const color = $derived(tag.color ?? 'muted');
|
|
24
|
+
const variant = $derived(tag.variant ?? 'subtle');
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<span
|
|
28
|
+
class="message-tag-chip"
|
|
29
|
+
data-color={color}
|
|
30
|
+
data-variant={variant}
|
|
31
|
+
aria-label={tag.type ? `${tag.type}: ${tag.label}` : undefined}
|
|
32
|
+
>
|
|
33
|
+
{#if tag.icon}
|
|
34
|
+
<Icon icon={tag.icon} class="message-tag-chip__icon" aria-hidden="true" />
|
|
35
|
+
{/if}
|
|
36
|
+
<span class="message-tag-chip__label">{tag.label}</span>
|
|
37
|
+
</span>
|
|
38
|
+
|
|
39
|
+
<style>
|
|
40
|
+
.message-tag-chip {
|
|
41
|
+
display: inline-flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
gap: var(--fd-space-3xs);
|
|
44
|
+
padding: 0 var(--fd-space-3xs);
|
|
45
|
+
border-radius: var(--fd-radius-sm);
|
|
46
|
+
font-family: var(--fd-font-mono);
|
|
47
|
+
font-size: var(--fd-text-2xs);
|
|
48
|
+
line-height: 1.4;
|
|
49
|
+
white-space: nowrap;
|
|
50
|
+
min-width: 0;
|
|
51
|
+
max-width: 100%;
|
|
52
|
+
background-color: var(--chip-bg);
|
|
53
|
+
color: var(--chip-fg);
|
|
54
|
+
border: 1px solid var(--chip-border, transparent);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.message-tag-chip__label {
|
|
58
|
+
overflow: hidden;
|
|
59
|
+
text-overflow: ellipsis;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.message-tag-chip :global(.message-tag-chip__icon) {
|
|
63
|
+
flex-shrink: 0;
|
|
64
|
+
font-size: 0.875em;
|
|
65
|
+
opacity: 0.8;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* Color hooks — one line per color. To add a color, add a row here. */
|
|
69
|
+
.message-tag-chip[data-color='muted'] {
|
|
70
|
+
--chip-c: var(--fd-muted-foreground);
|
|
71
|
+
--chip-c-on: var(--fd-background);
|
|
72
|
+
}
|
|
73
|
+
.message-tag-chip[data-color='primary'] {
|
|
74
|
+
--chip-c: var(--fd-primary);
|
|
75
|
+
--chip-c-on: var(--fd-primary-foreground);
|
|
76
|
+
}
|
|
77
|
+
.message-tag-chip[data-color='success'] {
|
|
78
|
+
--chip-c: var(--fd-success, oklch(55% 0.15 145));
|
|
79
|
+
--chip-c-on: white;
|
|
80
|
+
}
|
|
81
|
+
.message-tag-chip[data-color='warning'] {
|
|
82
|
+
--chip-c: var(--fd-warning);
|
|
83
|
+
--chip-c-on: var(--fd-background);
|
|
84
|
+
}
|
|
85
|
+
.message-tag-chip[data-color='error'] {
|
|
86
|
+
--chip-c: var(--fd-error);
|
|
87
|
+
--chip-c-on: white;
|
|
88
|
+
}
|
|
89
|
+
.message-tag-chip[data-color='info'] {
|
|
90
|
+
--chip-c: var(--fd-info);
|
|
91
|
+
--chip-c-on: var(--fd-background);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* Variants — derive bg/fg/border from --chip-c. */
|
|
95
|
+
.message-tag-chip[data-variant='subtle'] {
|
|
96
|
+
--chip-bg: color-mix(in srgb, var(--chip-c) 14%, transparent);
|
|
97
|
+
--chip-fg: var(--chip-c);
|
|
98
|
+
}
|
|
99
|
+
.message-tag-chip[data-variant='subtle'][data-color='muted'] {
|
|
100
|
+
/* Muted is the only color we render against the design's --fd-muted
|
|
101
|
+
surface for legibility; the color-mix path would lose contrast. */
|
|
102
|
+
--chip-bg: var(--fd-muted);
|
|
103
|
+
--chip-fg: var(--fd-muted-foreground);
|
|
104
|
+
}
|
|
105
|
+
.message-tag-chip[data-variant='solid'] {
|
|
106
|
+
--chip-bg: var(--chip-c);
|
|
107
|
+
--chip-fg: var(--chip-c-on);
|
|
108
|
+
}
|
|
109
|
+
.message-tag-chip[data-variant='outline'] {
|
|
110
|
+
--chip-bg: transparent;
|
|
111
|
+
--chip-fg: var(--chip-c);
|
|
112
|
+
--chip-border: var(--chip-c);
|
|
113
|
+
}
|
|
114
|
+
.message-tag-chip[data-variant='outline'][data-color='muted'] {
|
|
115
|
+
--chip-border: var(--fd-border);
|
|
116
|
+
}
|
|
117
|
+
</style>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { MessageTag } from '../../types/playground.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
tag: MessageTag;
|
|
4
|
+
}
|
|
5
|
+
declare const MessageTagChip: import("svelte").Component<Props, {}, "">;
|
|
6
|
+
type MessageTagChip = ReturnType<typeof MessageTagChip>;
|
|
7
|
+
export default MessageTagChip;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
MessageTagStrip Component
|
|
3
|
+
|
|
4
|
+
A flex-wrapped row of MessageTagChips. Encapsulates the layout the four
|
|
5
|
+
message variants used to copy individually.
|
|
6
|
+
-->
|
|
7
|
+
|
|
8
|
+
<script lang="ts">
|
|
9
|
+
import type { MessageTag } from '../../types/playground.js';
|
|
10
|
+
import MessageTagChip from './MessageTagChip.svelte';
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
tags: MessageTag[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let { tags }: Props = $props();
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
{#if tags.length > 0}
|
|
20
|
+
<!-- No role/label — each chip is already labelled (via tag.type when set)
|
|
21
|
+
and a one-element group adds nothing for AT users. -->
|
|
22
|
+
<span class="message-tag-strip">
|
|
23
|
+
{#each tags as tag (tag.id)}
|
|
24
|
+
<MessageTagChip {tag} />
|
|
25
|
+
{/each}
|
|
26
|
+
</span>
|
|
27
|
+
{/if}
|
|
28
|
+
|
|
29
|
+
<style>
|
|
30
|
+
.message-tag-strip {
|
|
31
|
+
display: inline-flex;
|
|
32
|
+
flex-wrap: wrap;
|
|
33
|
+
align-items: center;
|
|
34
|
+
gap: var(--fd-space-2xs);
|
|
35
|
+
min-width: 0;
|
|
36
|
+
}
|
|
37
|
+
</style>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { MessageTag } from '../../types/playground.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
tags: MessageTag[];
|
|
4
|
+
}
|
|
5
|
+
declare const MessageTagStrip: import("svelte").Component<Props, {}, "">;
|
|
6
|
+
type MessageTagStrip = ReturnType<typeof MessageTagStrip>;
|
|
7
|
+
export default MessageTagStrip;
|
|
@@ -2,10 +2,34 @@
|
|
|
2
2
|
import type { KanbanColumnDef } from '../../types/index.js';
|
|
3
3
|
|
|
4
4
|
const DEFAULT_COLUMNS: KanbanColumnDef[] = [
|
|
5
|
-
{
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
{
|
|
6
|
+
key: 'pending',
|
|
7
|
+
label: 'Pending',
|
|
8
|
+
statuses: ['idle', 'pending'],
|
|
9
|
+
icon: 'mdi:clock-outline',
|
|
10
|
+
color: 'var(--fd-muted-foreground)'
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
key: 'in_progress',
|
|
14
|
+
label: 'In Progress',
|
|
15
|
+
statuses: ['running', 'paused', 'interrupted'],
|
|
16
|
+
icon: 'mdi:play-circle-outline',
|
|
17
|
+
color: 'var(--fd-warning)'
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
key: 'done',
|
|
21
|
+
label: 'Done',
|
|
22
|
+
statuses: ['completed', 'skipped'],
|
|
23
|
+
icon: 'mdi:check-circle',
|
|
24
|
+
color: 'var(--fd-success)'
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
key: 'failed',
|
|
28
|
+
label: 'Failed',
|
|
29
|
+
statuses: ['failed', 'cancelled'],
|
|
30
|
+
icon: 'mdi:alert-circle',
|
|
31
|
+
color: 'var(--fd-error)'
|
|
32
|
+
}
|
|
9
33
|
];
|
|
10
34
|
</script>
|
|
11
35
|
|
|
@@ -13,7 +37,11 @@
|
|
|
13
37
|
import { onMount } from 'svelte';
|
|
14
38
|
import Icon from '@iconify/svelte';
|
|
15
39
|
import { createPipelineDataFetcher, resolveStatus } from './pipelineViewUtils.svelte.js';
|
|
16
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
getStatusLabel,
|
|
42
|
+
getStatusTextColor,
|
|
43
|
+
getStatusBackgroundColor
|
|
44
|
+
} from '../../utils/nodeStatus.js';
|
|
17
45
|
import type { NodeStatus } from './pipelineViewUtils.svelte.js';
|
|
18
46
|
import type { Workflow, WorkflowNode } from '../../types/index.js';
|
|
19
47
|
import type { EndpointConfig } from '../../config/endpoints.js';
|
|
@@ -27,6 +55,7 @@
|
|
|
27
55
|
|
|
28
56
|
let { pipelineId, workflow, endpointConfig, refreshTrigger = 0 }: Props = $props();
|
|
29
57
|
|
|
58
|
+
// svelte-ignore state_referenced_locally — endpointConfig is consumed once to build the API client; it must be stable
|
|
30
59
|
const fetcher = createPipelineDataFetcher(() => pipelineId, endpointConfig);
|
|
31
60
|
|
|
32
61
|
$effect(() => {
|
|
@@ -88,10 +117,7 @@
|
|
|
88
117
|
style="--col-color: {col.color ?? 'var(--fd-muted-foreground)'}"
|
|
89
118
|
>
|
|
90
119
|
<div class="pipeline-kanban__column-header">
|
|
91
|
-
<Icon
|
|
92
|
-
icon={col.icon ?? 'mdi:circle-outline'}
|
|
93
|
-
class="pipeline-kanban__col-icon"
|
|
94
|
-
/>
|
|
120
|
+
<Icon icon={col.icon ?? 'mdi:circle-outline'} class="pipeline-kanban__col-icon" />
|
|
95
121
|
<span class="pipeline-kanban__col-label">{col.label}</span>
|
|
96
122
|
<span class="pipeline-kanban__col-count">{items.length}</span>
|
|
97
123
|
</div>
|
|
@@ -104,8 +130,11 @@
|
|
|
104
130
|
{#if showStatusPill}
|
|
105
131
|
<span
|
|
106
132
|
class="pipeline-kanban__card-status"
|
|
107
|
-
style="color: {getStatusTextColor(
|
|
108
|
-
|
|
133
|
+
style="color: {getStatusTextColor(
|
|
134
|
+
status
|
|
135
|
+
)}; background-color: {getStatusBackgroundColor(status)}"
|
|
136
|
+
>{getStatusLabel(status)}</span
|
|
137
|
+
>
|
|
109
138
|
{/if}
|
|
110
139
|
</div>
|
|
111
140
|
<span class="pipeline-kanban__card-type">{node.data.metadata.id}</span>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
const VIEW_MODE_KEY = '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
|
-
type ViewMode = typeof BUILTIN_VIEWS[number] | (string & {});
|
|
5
|
+
type ViewMode = (typeof BUILTIN_VIEWS)[number] | (string & {});
|
|
6
6
|
</script>
|
|
7
7
|
|
|
8
8
|
<script lang="ts">
|
|
@@ -177,6 +177,7 @@
|
|
|
177
177
|
class="pipeline-panel__run-popover"
|
|
178
178
|
bind:this={runPopoverEl}
|
|
179
179
|
role="menu"
|
|
180
|
+
tabindex="-1"
|
|
180
181
|
onkeydown={(e) => {
|
|
181
182
|
if (e.key === 'Escape') {
|
|
182
183
|
runDropdownOpen = false;
|
|
@@ -415,6 +416,9 @@
|
|
|
415
416
|
right: 0;
|
|
416
417
|
z-index: 50;
|
|
417
418
|
min-width: 160px;
|
|
419
|
+
max-width: 320px;
|
|
420
|
+
max-height: min(60vh, 420px);
|
|
421
|
+
overflow-y: auto;
|
|
418
422
|
padding: var(--fd-space-xs);
|
|
419
423
|
background-color: var(--fd-background);
|
|
420
424
|
border: 1px solid var(--fd-border);
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
statusData: NodeStatusData | undefined;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
// svelte-ignore state_referenced_locally — endpointConfig is consumed once to build the API client; it must be stable
|
|
67
68
|
const fetcher = createPipelineDataFetcher(() => pipelineId, endpointConfig);
|
|
68
69
|
|
|
69
70
|
$effect(() => {
|
|
@@ -137,14 +138,24 @@
|
|
|
137
138
|
{#if expandable}
|
|
138
139
|
<Icon
|
|
139
140
|
icon="mdi:chevron-right"
|
|
140
|
-
class="pipeline-table__chevron {expanded
|
|
141
|
+
class="pipeline-table__chevron {expanded
|
|
142
|
+
? 'pipeline-table__chevron--open'
|
|
143
|
+
: ''}"
|
|
141
144
|
/>
|
|
142
145
|
{/if}
|
|
143
146
|
</td>
|
|
144
|
-
<td class="pipeline-table__td pipeline-table__td--label" title={row.node.data.label}
|
|
145
|
-
|
|
147
|
+
<td class="pipeline-table__td pipeline-table__td--label" title={row.node.data.label}
|
|
148
|
+
>{row.node.data.label}</td
|
|
149
|
+
>
|
|
150
|
+
<td
|
|
151
|
+
class="pipeline-table__td pipeline-table__td--muted"
|
|
152
|
+
title={row.node.data.metadata.id}>{row.node.data.metadata.id}</td
|
|
153
|
+
>
|
|
146
154
|
<td class="pipeline-table__td">
|
|
147
|
-
<span
|
|
155
|
+
<span
|
|
156
|
+
class="pipeline-table__status"
|
|
157
|
+
style="color: {getStatusTextColor(row.status)}"
|
|
158
|
+
>
|
|
148
159
|
<Icon
|
|
149
160
|
icon={STATUS_ICON[row.status] ?? 'mdi:circle-outline'}
|
|
150
161
|
class="pipeline-table__status-icon"
|
|
@@ -152,7 +163,9 @@
|
|
|
152
163
|
{row.status}
|
|
153
164
|
</span>
|
|
154
165
|
</td>
|
|
155
|
-
<td class="pipeline-table__td pipeline-table__td--id" title={row.node.id}
|
|
166
|
+
<td class="pipeline-table__td pipeline-table__td--id" title={row.node.id}
|
|
167
|
+
>{row.node.id}</td
|
|
168
|
+
>
|
|
156
169
|
</tr>
|
|
157
170
|
{#if expanded && expandable}
|
|
158
171
|
<tr class="pipeline-table__detail-row">
|
|
@@ -320,7 +333,8 @@
|
|
|
320
333
|
}
|
|
321
334
|
|
|
322
335
|
.pipeline-table__detail-cell {
|
|
323
|
-
padding: var(--fd-space-sm) var(--fd-space-md) var(--fd-space-sm)
|
|
336
|
+
padding: var(--fd-space-sm) var(--fd-space-md) var(--fd-space-sm)
|
|
337
|
+
calc(1.5rem + var(--fd-space-md));
|
|
324
338
|
border-bottom: 1px solid var(--fd-border);
|
|
325
339
|
}
|
|
326
340
|
|