@flowdrop/flowdrop 1.8.1 → 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.
- package/dist/components/PipelineStatus.svelte +15 -8
- package/dist/components/PipelineStatus.svelte.d.ts +3 -0
- package/dist/components/chat/AIChatPanel.svelte +16 -5
- package/dist/components/playground/ChatPanel.svelte +149 -24
- package/dist/components/playground/ChatPanel.svelte.d.ts +3 -1
- package/dist/components/playground/ExecutionList.svelte +142 -0
- package/dist/components/playground/ExecutionList.svelte.d.ts +10 -0
- package/dist/components/playground/MessageBubble.svelte +221 -153
- package/dist/components/playground/PipelinePanel.svelte +372 -0
- package/dist/components/playground/PipelinePanel.svelte.d.ts +19 -0
- package/dist/components/playground/Playground.svelte +586 -103
- package/dist/components/playground/Playground.svelte.d.ts +6 -0
- package/dist/services/globalSave.d.ts +7 -0
- package/dist/services/globalSave.js +5 -1
- package/dist/stores/pipelinePanelStore.svelte.d.ts +6 -0
- package/dist/stores/pipelinePanelStore.svelte.js +24 -0
- package/dist/stores/playgroundStore.svelte.d.ts +4 -0
- package/dist/stores/playgroundStore.svelte.js +58 -0
- package/dist/svelte-app.js +25 -2
- package/dist/types/playground.d.ts +12 -0
- package/package.json +1 -1
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
apiClient?: EnhancedFlowDropApiClient;
|
|
22
22
|
baseUrl?: string;
|
|
23
23
|
endpointConfig?: EndpointConfig;
|
|
24
|
+
runLabel?: string;
|
|
25
|
+
/** When true, suppresses breadcrumb and layout events (used inside playground panel) */
|
|
26
|
+
isEmbedded?: boolean;
|
|
24
27
|
onActionsReady?: (
|
|
25
28
|
actions: Array<{
|
|
26
29
|
label: string;
|
|
@@ -32,7 +35,7 @@
|
|
|
32
35
|
) => void;
|
|
33
36
|
}
|
|
34
37
|
|
|
35
|
-
let { pipelineId, workflow, apiClient, baseUrl, endpointConfig, onActionsReady }: Props =
|
|
38
|
+
let { pipelineId, workflow, apiClient, baseUrl, endpointConfig, onActionsReady, runLabel, isEmbedded = false }: Props =
|
|
36
39
|
$props();
|
|
37
40
|
|
|
38
41
|
// Initialize API client if not provided
|
|
@@ -213,8 +216,9 @@
|
|
|
213
216
|
};
|
|
214
217
|
});
|
|
215
218
|
|
|
216
|
-
// Send pipeline breadcrumbs to layout when they change
|
|
219
|
+
// Send pipeline breadcrumbs to layout when they change (skip when embedded in playground)
|
|
217
220
|
$effect(() => {
|
|
221
|
+
if (isEmbedded) return;
|
|
218
222
|
if (pipelineStatus && pipelineId && workflow) {
|
|
219
223
|
const sp = m().status.pipeline;
|
|
220
224
|
const breadcrumbs = [
|
|
@@ -239,7 +243,9 @@
|
|
|
239
243
|
icon: 'mdi:source-branch'
|
|
240
244
|
},
|
|
241
245
|
{
|
|
242
|
-
label:
|
|
246
|
+
label: runLabel
|
|
247
|
+
? `${runLabel} – ${pipelineStatus}`
|
|
248
|
+
: sp.pipelineCrumb({ id: pipelineId, status: pipelineStatus }),
|
|
243
249
|
icon: 'mdi:play-circle'
|
|
244
250
|
}
|
|
245
251
|
];
|
|
@@ -281,11 +287,11 @@
|
|
|
281
287
|
// In Svelte 5, $effect cleanup runs both on re-execution and component destroy.
|
|
282
288
|
</script>
|
|
283
289
|
|
|
284
|
-
<div class="pipeline-status-container">
|
|
290
|
+
<div class="pipeline-status-container" class:pipeline-status-container--embedded={isEmbedded}>
|
|
285
291
|
<!-- Workflow Visualization using App component -->
|
|
286
292
|
<App
|
|
287
293
|
{workflow}
|
|
288
|
-
height=
|
|
294
|
+
height={isEmbedded ? '100%' : '100vh'}
|
|
289
295
|
width="100%"
|
|
290
296
|
showNavbar={false}
|
|
291
297
|
disableSidebar={true}
|
|
@@ -310,8 +316,9 @@
|
|
|
310
316
|
background: var(--fd-layout-background, var(--fd-muted));
|
|
311
317
|
}
|
|
312
318
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
background:
|
|
319
|
+
.pipeline-status-container--embedded {
|
|
320
|
+
height: 100%;
|
|
321
|
+
background: var(--fd-muted);
|
|
322
|
+
--fd-layout-background: var(--fd-muted);
|
|
316
323
|
}
|
|
317
324
|
</style>
|
|
@@ -7,6 +7,9 @@ interface Props {
|
|
|
7
7
|
apiClient?: EnhancedFlowDropApiClient;
|
|
8
8
|
baseUrl?: string;
|
|
9
9
|
endpointConfig?: EndpointConfig;
|
|
10
|
+
runLabel?: string;
|
|
11
|
+
/** When true, suppresses breadcrumb and layout events (used inside playground panel) */
|
|
12
|
+
isEmbedded?: boolean;
|
|
10
13
|
onActionsReady?: (actions: Array<{
|
|
11
14
|
label: string;
|
|
12
15
|
href: string;
|
|
@@ -191,10 +191,22 @@
|
|
|
191
191
|
const msg = displayMessages[messageIndex];
|
|
192
192
|
if (!msg?.commandPreview) return;
|
|
193
193
|
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
const
|
|
194
|
+
// Refuse to run the batch if any command failed to parse. A corrupted batch
|
|
195
|
+
// (e.g. multiline set without """) causes partial execution and can hang the
|
|
196
|
+
// app — rejecting the whole batch is safer than executing the healthy subset.
|
|
197
|
+
const parseErrorCount = msg.commandPreview.filter((c) => c.status === 'error').length;
|
|
198
|
+
if (parseErrorCount > 0) {
|
|
199
|
+
for (const cmd of msg.commandPreview) {
|
|
200
|
+
if (cmd.status === 'pending') {
|
|
201
|
+
cmd.status = 'error';
|
|
202
|
+
cmd.result = 'Batch refused: fix parse errors before executing';
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
appendErrorToHistory(
|
|
206
|
+
`Batch was not executed: ${parseErrorCount} command${parseErrorCount > 1 ? 's have' : ' has'} parse errors. Dismiss this batch and ask the AI to provide corrected commands.`
|
|
207
|
+
);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
198
210
|
|
|
199
211
|
const context = getCommandContext();
|
|
200
212
|
if (!context) {
|
|
@@ -277,7 +289,6 @@
|
|
|
277
289
|
}
|
|
278
290
|
|
|
279
291
|
if (
|
|
280
|
-
!hadParseErrors &&
|
|
281
292
|
getBehaviorSettings().chatAutoRetry &&
|
|
282
293
|
workflowId &&
|
|
283
294
|
autoRetryCount < MAX_AUTO_RETRIES
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
getSessionStatus,
|
|
26
26
|
getCurrentSession
|
|
27
27
|
} from '../../stores/playgroundStore.svelte.js';
|
|
28
|
+
import type { PlaygroundExecution } from '../../types/playground.js';
|
|
28
29
|
import {
|
|
29
30
|
getInterruptsMap,
|
|
30
31
|
interruptActions,
|
|
@@ -74,6 +75,8 @@
|
|
|
74
75
|
* @default true
|
|
75
76
|
*/
|
|
76
77
|
compactSystemMessages?: boolean;
|
|
78
|
+
/** Whether log messages are visible — bindable so parent can host the toggle */
|
|
79
|
+
showLogs?: boolean;
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
let {
|
|
@@ -88,7 +91,8 @@
|
|
|
88
91
|
showChatInput = true,
|
|
89
92
|
showRunButton = true,
|
|
90
93
|
predefinedMessage,
|
|
91
|
-
compactSystemMessages = true
|
|
94
|
+
compactSystemMessages = true,
|
|
95
|
+
showLogs = $bindable(true)
|
|
92
96
|
}: Props = $props();
|
|
93
97
|
|
|
94
98
|
// Hoist playground branches — states/actions are read 8+ times each in the
|
|
@@ -125,9 +129,53 @@
|
|
|
125
129
|
let inputField = $state<HTMLTextAreaElement>();
|
|
126
130
|
|
|
127
131
|
/**
|
|
128
|
-
* Filter messages based on
|
|
132
|
+
* Filter messages based on local showLogs toggle.
|
|
133
|
+
* The showLogsInline prop is still honoured as the initial hint when explicitly set to false.
|
|
129
134
|
*/
|
|
130
|
-
const displayMessages = $derived(
|
|
135
|
+
const displayMessages = $derived(showLogs ? getMessages() : getChatMessages());
|
|
136
|
+
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
// Execution separators
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
|
|
141
|
+
type ChatItem =
|
|
142
|
+
| { type: 'message'; message: PlaygroundMessage; msgIndex: number }
|
|
143
|
+
| { type: 'separator'; key: string; label: string; status: PlaygroundExecution['status'] };
|
|
144
|
+
|
|
145
|
+
/** Map executionId → { label, status } derived from the current session */
|
|
146
|
+
const executionMeta = $derived(
|
|
147
|
+
new Map(
|
|
148
|
+
(getCurrentSession()?.executions ?? []).map((e, i) => [
|
|
149
|
+
e.id,
|
|
150
|
+
{ label: `Run #${i + 1}`, status: e.status }
|
|
151
|
+
])
|
|
152
|
+
)
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Interleave execution-boundary separators into the message list.
|
|
157
|
+
* A separator is inserted before the first message of each new execution.
|
|
158
|
+
*/
|
|
159
|
+
const chatItems = $derived(
|
|
160
|
+
(() => {
|
|
161
|
+
const items: ChatItem[] = [];
|
|
162
|
+
let lastExecId: string | null = null;
|
|
163
|
+
|
|
164
|
+
displayMessages.forEach((msg, i) => {
|
|
165
|
+
const execId = msg.executionId ?? null;
|
|
166
|
+
if (execId !== null && execId !== lastExecId) {
|
|
167
|
+
const meta = executionMeta.get(execId);
|
|
168
|
+
if (meta) {
|
|
169
|
+
items.push({ type: 'separator', key: `sep-${execId}`, label: meta.label, status: meta.status });
|
|
170
|
+
lastExecId = execId;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
items.push({ type: 'message', message: msg, msgIndex: i });
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
return items;
|
|
177
|
+
})()
|
|
178
|
+
);
|
|
131
179
|
|
|
132
180
|
/**
|
|
133
181
|
* Track previous message count for detecting new messages.
|
|
@@ -534,26 +582,50 @@
|
|
|
534
582
|
{/if}
|
|
535
583
|
</div>
|
|
536
584
|
{:else}
|
|
537
|
-
<!-- Messages -->
|
|
538
|
-
{#each
|
|
539
|
-
{#if
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
585
|
+
<!-- Messages with execution separators -->
|
|
586
|
+
{#each chatItems as item (item.type === 'message' ? item.message.id : item.key)}
|
|
587
|
+
{#if item.type === 'separator'}
|
|
588
|
+
<div
|
|
589
|
+
class="chat-panel__exec-sep"
|
|
590
|
+
class:chat-panel__exec-sep--completed={item.status === 'completed'}
|
|
591
|
+
class:chat-panel__exec-sep--failed={item.status === 'failed'}
|
|
592
|
+
class:chat-panel__exec-sep--running={item.status === 'running'}
|
|
593
|
+
>
|
|
594
|
+
<span class="chat-panel__exec-sep-line"></span>
|
|
595
|
+
<span class="chat-panel__exec-sep-label">
|
|
596
|
+
{#if item.status === 'completed'}
|
|
597
|
+
<Icon icon="mdi:check-circle" class="chat-panel__exec-sep-icon" />
|
|
598
|
+
{:else if item.status === 'failed'}
|
|
599
|
+
<Icon icon="mdi:alert-circle" class="chat-panel__exec-sep-icon" />
|
|
600
|
+
{:else}
|
|
601
|
+
<Icon icon="mdi:play-circle" class="chat-panel__exec-sep-icon" />
|
|
602
|
+
{/if}
|
|
603
|
+
{item.label}
|
|
604
|
+
<span class="chat-panel__exec-sep-status">{item.status}</span>
|
|
605
|
+
</span>
|
|
606
|
+
<span class="chat-panel__exec-sep-line"></span>
|
|
607
|
+
</div>
|
|
608
|
+
{:else}
|
|
609
|
+
{@const message = item.message}
|
|
610
|
+
{@const index = item.msgIndex}
|
|
611
|
+
{#if isInterruptMessage(message)}
|
|
612
|
+
{@const interrupt = getInterruptForMessage(message)}
|
|
613
|
+
{#if interrupt}
|
|
614
|
+
<InterruptBubble
|
|
615
|
+
{interrupt}
|
|
616
|
+
showTimestamp={showTimestamps}
|
|
617
|
+
onResolved={onInterruptResolved}
|
|
618
|
+
/>
|
|
619
|
+
{/if}
|
|
620
|
+
{:else}
|
|
621
|
+
<MessageBubble
|
|
622
|
+
{message}
|
|
545
623
|
showTimestamp={showTimestamps}
|
|
546
|
-
|
|
624
|
+
isLast={index === displayMessages.length - 1}
|
|
625
|
+
{enableMarkdown}
|
|
626
|
+
{compactSystemMessages}
|
|
547
627
|
/>
|
|
548
628
|
{/if}
|
|
549
|
-
{:else}
|
|
550
|
-
<MessageBubble
|
|
551
|
-
{message}
|
|
552
|
-
showTimestamp={showTimestamps}
|
|
553
|
-
isLast={index === displayMessages.length - 1}
|
|
554
|
-
{enableMarkdown}
|
|
555
|
-
{compactSystemMessages}
|
|
556
|
-
/>
|
|
557
629
|
{/if}
|
|
558
630
|
{/each}
|
|
559
631
|
|
|
@@ -644,6 +716,58 @@
|
|
|
644
716
|
background-color: var(--fd-background);
|
|
645
717
|
}
|
|
646
718
|
|
|
719
|
+
/* Execution separator */
|
|
720
|
+
.chat-panel__exec-sep {
|
|
721
|
+
display: flex;
|
|
722
|
+
align-items: center;
|
|
723
|
+
gap: var(--fd-space-sm);
|
|
724
|
+
padding: var(--fd-space-md) var(--fd-space-3xl);
|
|
725
|
+
margin: var(--fd-space-sm) 0;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
.chat-panel__exec-sep-line {
|
|
729
|
+
flex: 1;
|
|
730
|
+
height: 1px;
|
|
731
|
+
background-color: var(--fd-border);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
.chat-panel__exec-sep-label {
|
|
735
|
+
display: flex;
|
|
736
|
+
align-items: center;
|
|
737
|
+
gap: var(--fd-space-3xs);
|
|
738
|
+
font-size: var(--fd-text-xs);
|
|
739
|
+
font-weight: 600;
|
|
740
|
+
white-space: nowrap;
|
|
741
|
+
color: var(--fd-muted-foreground);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
:global(.chat-panel__exec-sep-icon) {
|
|
745
|
+
font-size: 0.875rem;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
.chat-panel__exec-sep--completed .chat-panel__exec-sep-label {
|
|
749
|
+
color: var(--fd-success, #16a34a);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
.chat-panel__exec-sep--completed .chat-panel__exec-sep-line {
|
|
753
|
+
background-color: var(--fd-success-muted, rgba(22, 163, 74, 0.2));
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
.chat-panel__exec-sep--failed .chat-panel__exec-sep-label {
|
|
757
|
+
color: var(--fd-error);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
.chat-panel__exec-sep--failed .chat-panel__exec-sep-line {
|
|
761
|
+
background-color: var(--fd-error-muted);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
.chat-panel__exec-sep-status {
|
|
765
|
+
text-transform: uppercase;
|
|
766
|
+
letter-spacing: 0.04em;
|
|
767
|
+
opacity: 0.8;
|
|
768
|
+
font-size: var(--fd-text-2xs);
|
|
769
|
+
}
|
|
770
|
+
|
|
647
771
|
/* Messages Container - Scrollable area that takes remaining space */
|
|
648
772
|
.chat-panel__messages {
|
|
649
773
|
flex: 1;
|
|
@@ -757,7 +881,7 @@
|
|
|
757
881
|
display: flex;
|
|
758
882
|
align-items: flex-end;
|
|
759
883
|
gap: var(--fd-space-md);
|
|
760
|
-
max-width:
|
|
884
|
+
max-width: 760px;
|
|
761
885
|
margin: 0 auto;
|
|
762
886
|
}
|
|
763
887
|
|
|
@@ -822,8 +946,9 @@
|
|
|
822
946
|
}
|
|
823
947
|
|
|
824
948
|
.chat-panel__send-btn:disabled {
|
|
825
|
-
background-color: var(--fd-
|
|
826
|
-
color: var(--fd-
|
|
949
|
+
background-color: var(--fd-foreground);
|
|
950
|
+
color: var(--fd-background);
|
|
951
|
+
opacity: 0.3;
|
|
827
952
|
cursor: not-allowed;
|
|
828
953
|
}
|
|
829
954
|
|
|
@@ -890,7 +1015,7 @@
|
|
|
890
1015
|
border-radius: var(--fd-radius-lg);
|
|
891
1016
|
color: var(--fd-muted-foreground);
|
|
892
1017
|
font-size: var(--fd-text-sm);
|
|
893
|
-
max-width:
|
|
1018
|
+
max-width: 760px;
|
|
894
1019
|
margin: 0 auto;
|
|
895
1020
|
}
|
|
896
1021
|
|
|
@@ -40,7 +40,9 @@ interface Props {
|
|
|
40
40
|
* @default true
|
|
41
41
|
*/
|
|
42
42
|
compactSystemMessages?: boolean;
|
|
43
|
+
/** Whether log messages are visible — bindable so parent can host the toggle */
|
|
44
|
+
showLogs?: boolean;
|
|
43
45
|
}
|
|
44
|
-
declare const ChatPanel: import("svelte").Component<Props, {}, "">;
|
|
46
|
+
declare const ChatPanel: import("svelte").Component<Props, {}, "showLogs">;
|
|
45
47
|
type ChatPanel = ReturnType<typeof ChatPanel>;
|
|
46
48
|
export default ChatPanel;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Icon from '@iconify/svelte';
|
|
3
|
+
import type { PlaygroundExecution } from '../../types/playground.js';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
executions: PlaygroundExecution[];
|
|
7
|
+
activeExecutionId: string | null;
|
|
8
|
+
latestExecutionId: string | null;
|
|
9
|
+
onSelect: (executionId: string) => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let { executions, activeExecutionId, latestExecutionId, onSelect }: Props = $props();
|
|
13
|
+
|
|
14
|
+
function label(index: number): string {
|
|
15
|
+
return `Run #${index + 1}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function statusIcon(status: PlaygroundExecution['status']): string {
|
|
19
|
+
if (status === 'completed') return 'mdi:check-circle';
|
|
20
|
+
if (status === 'failed') return 'mdi:alert-circle';
|
|
21
|
+
return '';
|
|
22
|
+
}
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<div class="execution-list">
|
|
26
|
+
{#each executions as execution, i (execution.id)}
|
|
27
|
+
<div
|
|
28
|
+
class="execution-list__item"
|
|
29
|
+
class:execution-list__item--active={execution.id === activeExecutionId}
|
|
30
|
+
class:execution-list__item--running={execution.status === 'running'}
|
|
31
|
+
class:execution-list__item--completed={execution.status === 'completed'}
|
|
32
|
+
class:execution-list__item--failed={execution.status === 'failed'}
|
|
33
|
+
role="button"
|
|
34
|
+
tabindex="0"
|
|
35
|
+
onclick={() => onSelect(execution.id)}
|
|
36
|
+
onkeydown={(e) => e.key === 'Enter' && onSelect(execution.id)}
|
|
37
|
+
>
|
|
38
|
+
{#if execution.status === 'running'}
|
|
39
|
+
<span class="execution-list__running-dot" aria-hidden="true"></span>
|
|
40
|
+
{:else if statusIcon(execution.status)}
|
|
41
|
+
<Icon
|
|
42
|
+
icon={statusIcon(execution.status)}
|
|
43
|
+
class="execution-list__status-icon execution-list__status-icon--{execution.status}"
|
|
44
|
+
/>
|
|
45
|
+
{/if}
|
|
46
|
+
<span class="execution-list__label">{label(i)}</span>
|
|
47
|
+
{#if execution.id === latestExecutionId}
|
|
48
|
+
<span class="execution-list__badge">latest</span>
|
|
49
|
+
{/if}
|
|
50
|
+
</div>
|
|
51
|
+
{/each}
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<style>
|
|
55
|
+
/* Match the visual weight of .playground__session items */
|
|
56
|
+
.execution-list {
|
|
57
|
+
display: flex;
|
|
58
|
+
flex-direction: column;
|
|
59
|
+
gap: var(--fd-space-3xs);
|
|
60
|
+
padding: 0 var(--fd-space-sm);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.execution-list__item {
|
|
64
|
+
display: flex;
|
|
65
|
+
align-items: center;
|
|
66
|
+
gap: var(--fd-space-xs);
|
|
67
|
+
padding: var(--fd-space-sm) var(--fd-space-md);
|
|
68
|
+
border-radius: var(--fd-radius-md);
|
|
69
|
+
border-left: 3px solid transparent;
|
|
70
|
+
cursor: pointer;
|
|
71
|
+
font-size: var(--fd-text-sm);
|
|
72
|
+
color: var(--fd-foreground);
|
|
73
|
+
transition:
|
|
74
|
+
background-color var(--fd-transition-fast),
|
|
75
|
+
border-left-color var(--fd-transition-fast);
|
|
76
|
+
user-select: none;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.execution-list__item:hover {
|
|
80
|
+
background-color: var(--fd-muted);
|
|
81
|
+
border-left-color: var(--fd-border);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.execution-list__item--active {
|
|
85
|
+
background-color: var(--fd-primary-muted);
|
|
86
|
+
border-left-color: var(--fd-primary);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.execution-list__item--active:hover {
|
|
90
|
+
background-color: var(--fd-primary-muted);
|
|
91
|
+
border-left-color: var(--fd-primary);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.execution-list__label {
|
|
95
|
+
flex: 1;
|
|
96
|
+
min-width: 0;
|
|
97
|
+
overflow: hidden;
|
|
98
|
+
text-overflow: ellipsis;
|
|
99
|
+
white-space: nowrap;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.execution-list__item--active .execution-list__label {
|
|
103
|
+
color: var(--fd-primary);
|
|
104
|
+
font-weight: 500;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.execution-list__badge {
|
|
108
|
+
font-size: var(--fd-text-2xs);
|
|
109
|
+
font-weight: 600;
|
|
110
|
+
text-transform: uppercase;
|
|
111
|
+
letter-spacing: 0.04em;
|
|
112
|
+
color: var(--fd-success);
|
|
113
|
+
flex-shrink: 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.execution-list__running-dot {
|
|
117
|
+
width: 6px;
|
|
118
|
+
height: 6px;
|
|
119
|
+
border-radius: 50%;
|
|
120
|
+
background-color: var(--fd-success);
|
|
121
|
+
flex-shrink: 0;
|
|
122
|
+
animation: pulse 1.5s ease-in-out infinite;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@keyframes pulse {
|
|
126
|
+
0%, 100% { opacity: 1; }
|
|
127
|
+
50% { opacity: 0.4; }
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
:global(.execution-list__status-icon) {
|
|
131
|
+
flex-shrink: 0;
|
|
132
|
+
font-size: var(--fd-text-sm);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
:global(.execution-list__status-icon--completed) {
|
|
136
|
+
color: var(--fd-success, #22c55e);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
:global(.execution-list__status-icon--failed) {
|
|
140
|
+
color: var(--fd-error, #ef4444);
|
|
141
|
+
}
|
|
142
|
+
</style>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PlaygroundExecution } from '../../types/playground.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
executions: PlaygroundExecution[];
|
|
4
|
+
activeExecutionId: string | null;
|
|
5
|
+
latestExecutionId: string | null;
|
|
6
|
+
onSelect: (executionId: string) => void;
|
|
7
|
+
}
|
|
8
|
+
declare const ExecutionList: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type ExecutionList = ReturnType<typeof ExecutionList>;
|
|
10
|
+
export default ExecutionList;
|