@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.
- package/dist/chat/responseParser.js +7 -0
- package/dist/commands/parser.js +12 -0
- package/dist/components/PipelineStatus.svelte +15 -8
- package/dist/components/PipelineStatus.svelte.d.ts +3 -0
- package/dist/components/chat/AIChatPanel.svelte +22 -1
- 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
|
@@ -82,6 +82,13 @@ export function extractCommands(llmResponse) {
|
|
|
82
82
|
currentExplanation.push(line);
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
|
+
// Flush dangling multiline buffer (LLM never closed """ and the response
|
|
86
|
+
// ended before any closing fence). Surface as a command so the parser
|
|
87
|
+
// produces a visible error rather than silently dropping the content.
|
|
88
|
+
if (multilineBuffer !== null) {
|
|
89
|
+
commands.push(multilineBuffer.join('\n'));
|
|
90
|
+
multilineBuffer = null;
|
|
91
|
+
}
|
|
85
92
|
// Flush remaining explanation text
|
|
86
93
|
if (currentExplanation.length > 0) {
|
|
87
94
|
explanationParts.push(currentExplanation.join('\n'));
|
package/dist/commands/parser.js
CHANGED
|
@@ -259,6 +259,18 @@ export function parseCommand(input) {
|
|
|
259
259
|
if (!trimmed) {
|
|
260
260
|
return { ok: false, error: 'Empty command', input };
|
|
261
261
|
}
|
|
262
|
+
// Detect an unclosed multiline """ block — common when a low-quality LLM
|
|
263
|
+
// omits the closing """ on its own line. The opener pattern is `"""\n`
|
|
264
|
+
// (triple-quote followed by a newline), and a well-formed value must end
|
|
265
|
+
// with `"""`. If we see the opener but not the closer, surface a clear
|
|
266
|
+
// error instead of falling through to a generic "Invalid syntax".
|
|
267
|
+
if (trimmed.includes('"""\n') && !trimmed.endsWith('"""')) {
|
|
268
|
+
return {
|
|
269
|
+
ok: false,
|
|
270
|
+
error: 'Unclosed """ block — missing closing """ on its own line',
|
|
271
|
+
input
|
|
272
|
+
};
|
|
273
|
+
}
|
|
262
274
|
for (const rule of rules) {
|
|
263
275
|
const match = trimmed.match(rule.pattern);
|
|
264
276
|
if (match) {
|
|
@@ -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,6 +191,23 @@
|
|
|
191
191
|
const msg = displayMessages[messageIndex];
|
|
192
192
|
if (!msg?.commandPreview) return;
|
|
193
193
|
|
|
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
|
+
}
|
|
210
|
+
|
|
194
211
|
const context = getCommandContext();
|
|
195
212
|
if (!context) {
|
|
196
213
|
for (const cmd of msg.commandPreview) {
|
|
@@ -271,7 +288,11 @@
|
|
|
271
288
|
return;
|
|
272
289
|
}
|
|
273
290
|
|
|
274
|
-
if (
|
|
291
|
+
if (
|
|
292
|
+
getBehaviorSettings().chatAutoRetry &&
|
|
293
|
+
workflowId &&
|
|
294
|
+
autoRetryCount < MAX_AUTO_RETRIES
|
|
295
|
+
) {
|
|
275
296
|
autoRetryCount++;
|
|
276
297
|
const errorText = buildBatchErrorMessage(
|
|
277
298
|
completedCount,
|
|
@@ -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;
|