@flowdrop/flowdrop 1.9.0 → 1.11.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/api/enhanced-client.js +5 -1
- package/dist/components/Navbar.svelte +1 -10
- package/dist/components/Navbar.svelte.d.ts +1 -9
- package/dist/components/PipelineStatus.svelte +17 -1
- package/dist/components/PipelineStatus.svelte.d.ts +2 -0
- package/dist/components/WorkflowEditor.svelte +26 -0
- package/dist/components/playground/ChatPanel.svelte +33 -235
- package/dist/components/playground/ExecutionList.svelte +2 -6
- package/dist/components/playground/MessageBubble.svelte +61 -4
- package/dist/components/playground/PipelinePanel.svelte +17 -7
- package/dist/components/playground/PipelinePanel.svelte.d.ts +2 -1
- package/dist/components/playground/Playground.svelte +123 -73
- package/dist/components/playground/PlaygroundApp.svelte +110 -0
- package/dist/components/playground/PlaygroundApp.svelte.d.ts +28 -0
- package/dist/components/playground/PlaygroundStudio.svelte +404 -0
- package/dist/components/playground/PlaygroundStudio.svelte.d.ts +30 -0
- package/dist/editor/index.d.ts +1 -1
- package/dist/editor/index.js +1 -1
- package/dist/playground/index.d.ts +8 -3
- package/dist/playground/index.js +15 -5
- package/dist/playground/mount.d.ts +63 -4
- package/dist/playground/mount.js +173 -84
- package/dist/services/nodeExecutionService.js +4 -2
- package/dist/services/playgroundService.d.ts +11 -4
- package/dist/services/playgroundService.js +22 -12
- package/dist/stores/playgroundStore.svelte.d.ts +22 -21
- package/dist/stores/playgroundStore.svelte.js +79 -58
- package/dist/svelte-app.d.ts +2 -10
- package/dist/types/navbar.d.ts +14 -0
- package/dist/types/navbar.js +1 -0
- package/dist/types/playground.d.ts +3 -5
- package/package.json +1 -1
package/dist/playground/mount.js
CHANGED
|
@@ -46,9 +46,76 @@
|
|
|
46
46
|
import { mount, unmount } from 'svelte';
|
|
47
47
|
import Playground from '../components/playground/Playground.svelte';
|
|
48
48
|
import PlaygroundModal from '../components/playground/PlaygroundModal.svelte';
|
|
49
|
+
import PlaygroundStudio from '../components/playground/PlaygroundStudio.svelte';
|
|
50
|
+
import PlaygroundApp from '../components/playground/PlaygroundApp.svelte';
|
|
51
|
+
import { initializeSettings } from '../stores/settingsStore.svelte.js';
|
|
49
52
|
import { setEndpointConfig } from '../services/api.js';
|
|
50
53
|
import { playgroundService } from '../services/playgroundService.js';
|
|
51
|
-
import { getCurrentSession, getSessions, getMessages, playgroundActions,
|
|
54
|
+
import { getCurrentSession, getSessions, getMessages, getIsExecuting, playgroundActions, applyServerResponse, subscribeToSessionStatus } from '../stores/playgroundStore.svelte.js';
|
|
55
|
+
async function resolveEndpointConfig(endpointConfig) {
|
|
56
|
+
if (!endpointConfig)
|
|
57
|
+
return undefined;
|
|
58
|
+
const { defaultEndpointConfig } = await import('../config/endpoints.js');
|
|
59
|
+
const resolved = {
|
|
60
|
+
...defaultEndpointConfig,
|
|
61
|
+
...endpointConfig,
|
|
62
|
+
endpoints: { ...defaultEndpointConfig.endpoints, ...endpointConfig.endpoints }
|
|
63
|
+
};
|
|
64
|
+
setEndpointConfig(resolved);
|
|
65
|
+
return resolved;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Shared prelude for the playground mount functions: resolves the endpoint
|
|
69
|
+
* config and initializes settings/theme. Each mount function still owns its
|
|
70
|
+
* own argument validation and container sizing (those vary per mode).
|
|
71
|
+
*/
|
|
72
|
+
async function prepareMount(options) {
|
|
73
|
+
const finalEndpointConfig = await resolveEndpointConfig(options.endpointConfig);
|
|
74
|
+
await initializeSettings({ defaults: options.settings });
|
|
75
|
+
return finalEndpointConfig;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Apply caller-supplied height/width as inline styles. When omitted, the host
|
|
79
|
+
* CSS owns container sizing — this is deliberate: a default of `100%` does not
|
|
80
|
+
* resolve inside parents with only `min-height` set (a common pattern in
|
|
81
|
+
* Drupal admin chrome), collapsing the playground to zero.
|
|
82
|
+
*/
|
|
83
|
+
function sizeContainer(container, height, width) {
|
|
84
|
+
if (height !== undefined)
|
|
85
|
+
container.style.height = height;
|
|
86
|
+
if (width !== undefined)
|
|
87
|
+
container.style.width = width;
|
|
88
|
+
}
|
|
89
|
+
function buildMountedPlayground(svelteApp, workflowId, config, onSessionStatusChange) {
|
|
90
|
+
const pollingInterval = config.pollingInterval ?? 1500;
|
|
91
|
+
const unsubscribeStatus = onSessionStatusChange
|
|
92
|
+
? subscribeToSessionStatus(onSessionStatusChange)
|
|
93
|
+
: undefined;
|
|
94
|
+
return {
|
|
95
|
+
destroy: () => {
|
|
96
|
+
unsubscribeStatus?.();
|
|
97
|
+
playgroundService.stopPolling();
|
|
98
|
+
playgroundActions.reset();
|
|
99
|
+
unmount(svelteApp);
|
|
100
|
+
},
|
|
101
|
+
getCurrentSession: () => getCurrentSession(),
|
|
102
|
+
getSessions: () => getSessions(),
|
|
103
|
+
getMessageCount: () => getMessages().length,
|
|
104
|
+
isExecuting: () => getIsExecuting(),
|
|
105
|
+
stopPolling: () => playgroundService.stopPolling(),
|
|
106
|
+
startPolling: () => {
|
|
107
|
+
const session = getCurrentSession();
|
|
108
|
+
if (session) {
|
|
109
|
+
playgroundService.startPolling(session.id, (response) => applyServerResponse(response), pollingInterval, config.shouldStopPolling, playgroundService.getLastSequenceNumber());
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
pushMessages: (response) => applyServerResponse(response),
|
|
113
|
+
reset: () => {
|
|
114
|
+
playgroundService.stopPolling();
|
|
115
|
+
playgroundActions.reset();
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
52
119
|
/**
|
|
53
120
|
* Mount the Playground component in a container
|
|
54
121
|
*
|
|
@@ -79,7 +146,7 @@ import { getCurrentSession, getSessions, getMessages, playgroundActions, createP
|
|
|
79
146
|
* ```
|
|
80
147
|
*/
|
|
81
148
|
export async function mountPlayground(container, options) {
|
|
82
|
-
const { workflowId, workflow, mode = 'standalone', initialSessionId, endpointConfig, config = {}, height
|
|
149
|
+
const { workflowId, workflow, mode = 'standalone', initialSessionId, endpointConfig, config = {}, height, width, settings: initialSettings, onClose, onSessionStatusChange } = options;
|
|
83
150
|
// Validate required parameters
|
|
84
151
|
if (!workflowId) {
|
|
85
152
|
throw new Error('workflowId is required for mountPlayground()');
|
|
@@ -91,37 +158,17 @@ export async function mountPlayground(container, options) {
|
|
|
91
158
|
if (mode === 'modal' && !onClose) {
|
|
92
159
|
throw new Error('onClose callback is required for modal mode');
|
|
93
160
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const { defaultEndpointConfig } = await import('../config/endpoints.js');
|
|
99
|
-
finalEndpointConfig = {
|
|
100
|
-
...defaultEndpointConfig,
|
|
101
|
-
...endpointConfig,
|
|
102
|
-
endpoints: {
|
|
103
|
-
...defaultEndpointConfig.endpoints,
|
|
104
|
-
...endpointConfig.endpoints
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
setEndpointConfig(finalEndpointConfig);
|
|
108
|
-
}
|
|
109
|
-
// Handle modal mode differently
|
|
110
|
-
// For modal mode, PlaygroundModal creates its own backdrop, so we mount directly to body
|
|
111
|
-
// For other modes, use the provided container
|
|
161
|
+
const finalEndpointConfig = await prepareMount({
|
|
162
|
+
endpointConfig,
|
|
163
|
+
settings: initialSettings
|
|
164
|
+
});
|
|
112
165
|
let targetContainer = container;
|
|
113
166
|
if (mode === 'modal') {
|
|
114
|
-
// For modal mode, create a container in the body
|
|
115
|
-
// PlaygroundModal will handle the backdrop itself
|
|
116
167
|
targetContainer = document.body;
|
|
117
168
|
}
|
|
118
169
|
else {
|
|
119
|
-
|
|
120
|
-
container.style.height = height;
|
|
121
|
-
container.style.width = width;
|
|
170
|
+
sizeContainer(container, height, width);
|
|
122
171
|
}
|
|
123
|
-
// Mount the appropriate component
|
|
124
|
-
// Separate the mount calls to avoid TypeScript inference issues with different props types
|
|
125
172
|
let svelteApp;
|
|
126
173
|
if (mode === 'modal') {
|
|
127
174
|
svelteApp = mount(PlaygroundModal, {
|
|
@@ -155,63 +202,7 @@ export async function mountPlayground(container, options) {
|
|
|
155
202
|
}
|
|
156
203
|
});
|
|
157
204
|
}
|
|
158
|
-
|
|
159
|
-
const state = {
|
|
160
|
-
svelteApp,
|
|
161
|
-
container: targetContainer,
|
|
162
|
-
originalContainer: mode === 'modal' ? container : undefined,
|
|
163
|
-
workflowId
|
|
164
|
-
};
|
|
165
|
-
// Create shared polling callback using lifecycle hooks from config
|
|
166
|
-
const pollingCallback = createPollingCallback(config.isTerminalStatus);
|
|
167
|
-
const pollingInterval = config.pollingInterval ?? 1500;
|
|
168
|
-
// Subscribe to session status changes if callback provided
|
|
169
|
-
let unsubscribeStatus;
|
|
170
|
-
if (onSessionStatusChange) {
|
|
171
|
-
unsubscribeStatus = subscribeToSessionStatus(onSessionStatusChange);
|
|
172
|
-
}
|
|
173
|
-
// Create the mounted playground interface
|
|
174
|
-
const mountedPlayground = {
|
|
175
|
-
destroy: () => {
|
|
176
|
-
// Unsubscribe from status changes
|
|
177
|
-
unsubscribeStatus?.();
|
|
178
|
-
// Stop any active polling
|
|
179
|
-
playgroundService.stopPolling();
|
|
180
|
-
// Reset playground state
|
|
181
|
-
playgroundActions.reset();
|
|
182
|
-
// Unmount Svelte component
|
|
183
|
-
unmount(state.svelteApp);
|
|
184
|
-
},
|
|
185
|
-
getCurrentSession: () => {
|
|
186
|
-
return getCurrentSession();
|
|
187
|
-
},
|
|
188
|
-
getSessions: () => {
|
|
189
|
-
return getSessions();
|
|
190
|
-
},
|
|
191
|
-
getMessageCount: () => {
|
|
192
|
-
return getMessages().length;
|
|
193
|
-
},
|
|
194
|
-
isExecuting: () => {
|
|
195
|
-
return playgroundService.isPolling();
|
|
196
|
-
},
|
|
197
|
-
stopPolling: () => {
|
|
198
|
-
playgroundService.stopPolling();
|
|
199
|
-
},
|
|
200
|
-
startPolling: () => {
|
|
201
|
-
const session = getCurrentSession();
|
|
202
|
-
if (session) {
|
|
203
|
-
playgroundService.startPolling(session.id, pollingCallback, pollingInterval, config.shouldStopPolling);
|
|
204
|
-
}
|
|
205
|
-
},
|
|
206
|
-
pushMessages: (response) => {
|
|
207
|
-
pollingCallback(response);
|
|
208
|
-
},
|
|
209
|
-
reset: () => {
|
|
210
|
-
playgroundService.stopPolling();
|
|
211
|
-
playgroundActions.reset();
|
|
212
|
-
}
|
|
213
|
-
};
|
|
214
|
-
return mountedPlayground;
|
|
205
|
+
return buildMountedPlayground(svelteApp, workflowId, config, onSessionStatusChange);
|
|
215
206
|
}
|
|
216
207
|
/**
|
|
217
208
|
* Unmount a Playground instance
|
|
@@ -233,3 +224,101 @@ export function unmountPlayground(app) {
|
|
|
233
224
|
app.destroy();
|
|
234
225
|
}
|
|
235
226
|
}
|
|
227
|
+
export async function mountPlaygroundStudio(container, options) {
|
|
228
|
+
const { workflowId, workflow, mode = 'standalone', initialSessionId, endpointConfig, config = {}, height, width, initialPipelineOpen, minChatWidth, initialPipelineWidth, settings: initialSettings, onClose, onSessionNavigate, onSessionStatusChange } = options;
|
|
229
|
+
if (!workflowId) {
|
|
230
|
+
throw new Error('workflowId is required for mountPlaygroundStudio()');
|
|
231
|
+
}
|
|
232
|
+
if (!container) {
|
|
233
|
+
throw new Error('container element is required for mountPlaygroundStudio()');
|
|
234
|
+
}
|
|
235
|
+
if (mode === 'modal') {
|
|
236
|
+
throw new Error('modal mode is not supported by mountPlaygroundStudio() — use mountPlayground() instead');
|
|
237
|
+
}
|
|
238
|
+
const finalEndpointConfig = await prepareMount({
|
|
239
|
+
endpointConfig,
|
|
240
|
+
settings: initialSettings
|
|
241
|
+
});
|
|
242
|
+
sizeContainer(container, height, width);
|
|
243
|
+
const svelteApp = mount(PlaygroundStudio, {
|
|
244
|
+
target: container,
|
|
245
|
+
props: {
|
|
246
|
+
workflowId,
|
|
247
|
+
workflow,
|
|
248
|
+
mode,
|
|
249
|
+
initialSessionId,
|
|
250
|
+
endpointConfig: finalEndpointConfig,
|
|
251
|
+
config,
|
|
252
|
+
onClose,
|
|
253
|
+
onSessionNavigate,
|
|
254
|
+
initialPipelineOpen,
|
|
255
|
+
minChatWidth,
|
|
256
|
+
initialPipelineWidth
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
return buildMountedPlayground(svelteApp, workflowId, config, onSessionStatusChange);
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Mount the full-page PlaygroundApp (Navbar + PlaygroundStudio) into a container.
|
|
263
|
+
*
|
|
264
|
+
* Use this when you want the same chrome as the FlowDrop editor — logo,
|
|
265
|
+
* branding, and settings modal — wrapped around the playground. For an
|
|
266
|
+
* embeddable split-pane without the navbar, use mountPlaygroundStudio().
|
|
267
|
+
*
|
|
268
|
+
* @example
|
|
269
|
+
* ```typescript
|
|
270
|
+
* const app = await mountPlaygroundApp(container, {
|
|
271
|
+
* workflowId: 'wf-123',
|
|
272
|
+
* endpointConfig: createEndpointConfig('/api/flowdrop'),
|
|
273
|
+
* navbarTitle: 'My Workflow',
|
|
274
|
+
* primaryActions: [
|
|
275
|
+
* { label: 'Edit', href: '/workflows/wf-123/edit', icon: 'mdi:pencil-outline', variant: 'secondary' },
|
|
276
|
+
* { label: 'Workflows', href: '/workflows', icon: 'mdi:arrow-left', variant: 'outline' }
|
|
277
|
+
* ]
|
|
278
|
+
* });
|
|
279
|
+
* ```
|
|
280
|
+
*/
|
|
281
|
+
export async function mountPlaygroundApp(container, options) {
|
|
282
|
+
const { workflowId, workflow, mode = 'standalone', initialSessionId, endpointConfig, config = {}, height, width, showNavbar = true, navbarTitle, primaryActions, showSettings = true, settingsCategories, showSettingsSyncButton, showSettingsResetButton, initialPipelineOpen, minChatWidth, initialPipelineWidth, settings: initialSettings, onClose, onSessionNavigate, onSessionStatusChange } = options;
|
|
283
|
+
if (!workflowId) {
|
|
284
|
+
throw new Error('workflowId is required for mountPlaygroundApp()');
|
|
285
|
+
}
|
|
286
|
+
if (!container) {
|
|
287
|
+
throw new Error('container element is required for mountPlaygroundApp()');
|
|
288
|
+
}
|
|
289
|
+
// Positive narrowing (not `=== 'modal'`) because PlaygroundAppMountOptions
|
|
290
|
+
// Omit-narrows `mode`, so the type lies for JS callers — check the values
|
|
291
|
+
// we actually accept instead of the one we don't.
|
|
292
|
+
if (mode !== 'standalone' && mode !== 'embedded') {
|
|
293
|
+
throw new Error(`mountPlaygroundApp(): mode must be 'standalone' or 'embedded', got ${String(mode)}`);
|
|
294
|
+
}
|
|
295
|
+
const finalEndpointConfig = await prepareMount({
|
|
296
|
+
endpointConfig,
|
|
297
|
+
settings: initialSettings
|
|
298
|
+
});
|
|
299
|
+
sizeContainer(container, height, width);
|
|
300
|
+
const svelteApp = mount(PlaygroundApp, {
|
|
301
|
+
target: container,
|
|
302
|
+
props: {
|
|
303
|
+
workflowId,
|
|
304
|
+
workflow,
|
|
305
|
+
mode,
|
|
306
|
+
initialSessionId,
|
|
307
|
+
endpointConfig: finalEndpointConfig,
|
|
308
|
+
config,
|
|
309
|
+
showNavbar,
|
|
310
|
+
navbarTitle,
|
|
311
|
+
primaryActions,
|
|
312
|
+
showSettings,
|
|
313
|
+
settingsCategories,
|
|
314
|
+
showSettingsSyncButton,
|
|
315
|
+
showSettingsResetButton,
|
|
316
|
+
initialPipelineOpen,
|
|
317
|
+
minChatWidth,
|
|
318
|
+
initialPipelineWidth,
|
|
319
|
+
onClose,
|
|
320
|
+
onSessionNavigate
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
return buildMountedPlayground(svelteApp, workflowId, config, onSessionStatusChange);
|
|
324
|
+
}
|
|
@@ -41,7 +41,8 @@ export class NodeExecutionService {
|
|
|
41
41
|
if (!response.ok) {
|
|
42
42
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
43
43
|
}
|
|
44
|
-
const
|
|
44
|
+
const raw = await response.json();
|
|
45
|
+
const pipelineData = raw.data ?? raw;
|
|
45
46
|
const jobs = pipelineData.jobs || [];
|
|
46
47
|
const nodeStatuses = pipelineData.node_statuses || {};
|
|
47
48
|
// Find the job for this node
|
|
@@ -116,7 +117,8 @@ export class NodeExecutionService {
|
|
|
116
117
|
}
|
|
117
118
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
118
119
|
}
|
|
119
|
-
const
|
|
120
|
+
const raw = await response.json();
|
|
121
|
+
const result = raw.data ?? raw;
|
|
120
122
|
const jobs = result.jobs || [];
|
|
121
123
|
const executionInfoMap = {};
|
|
122
124
|
// Initialize all nodes with default values
|
|
@@ -18,7 +18,7 @@ export declare class PlaygroundService {
|
|
|
18
18
|
private pollingInterval;
|
|
19
19
|
private pollingSessionId;
|
|
20
20
|
private currentBackoff;
|
|
21
|
-
private
|
|
21
|
+
private lastSequenceNumber;
|
|
22
22
|
private constructor();
|
|
23
23
|
/**
|
|
24
24
|
* Get the singleton instance of PlaygroundService
|
|
@@ -78,11 +78,11 @@ export declare class PlaygroundService {
|
|
|
78
78
|
* Get messages from a playground session
|
|
79
79
|
*
|
|
80
80
|
* @param sessionId - The session UUID
|
|
81
|
-
* @param
|
|
81
|
+
* @param afterSequence - Optional sequence number cursor — returns only messages with sequenceNumber > this value
|
|
82
82
|
* @param limit - Maximum number of messages to return
|
|
83
83
|
* @returns Messages and session status
|
|
84
84
|
*/
|
|
85
|
-
getMessages(sessionId: string,
|
|
85
|
+
getMessages(sessionId: string, afterSequence?: number, limit?: number): Promise<PlaygroundMessagesApiResponse>;
|
|
86
86
|
/**
|
|
87
87
|
* Send a message to a playground session
|
|
88
88
|
*
|
|
@@ -105,8 +105,9 @@ export declare class PlaygroundService {
|
|
|
105
105
|
* @param callback - Callback function to handle new messages
|
|
106
106
|
* @param interval - Polling interval in milliseconds (default: 1500)
|
|
107
107
|
* @param shouldStopPolling - Optional override for stop conditions (default: defaultShouldStopPolling)
|
|
108
|
+
* @param initialSequenceNumber - Optional sequence number to seed polling from (avoids re-fetching already loaded messages)
|
|
108
109
|
*/
|
|
109
|
-
startPolling(sessionId: string, callback: (response: PlaygroundMessagesApiResponse) => void, interval?: number, shouldStopPolling?: (status: PlaygroundSessionStatus) => boolean): void;
|
|
110
|
+
startPolling(sessionId: string, callback: (response: PlaygroundMessagesApiResponse) => void, interval?: number, shouldStopPolling?: (status: PlaygroundSessionStatus) => boolean, initialSequenceNumber?: number | null): void;
|
|
110
111
|
/**
|
|
111
112
|
* Stop polling for messages
|
|
112
113
|
*/
|
|
@@ -123,6 +124,12 @@ export declare class PlaygroundService {
|
|
|
123
124
|
* @returns The session ID being polled, or null
|
|
124
125
|
*/
|
|
125
126
|
getPollingSessionId(): string | null;
|
|
127
|
+
/**
|
|
128
|
+
* Get the last sequence number used as cursor for incremental polling
|
|
129
|
+
*
|
|
130
|
+
* @returns The last sequence number, or null
|
|
131
|
+
*/
|
|
132
|
+
getLastSequenceNumber(): number | null;
|
|
126
133
|
}
|
|
127
134
|
/**
|
|
128
135
|
* Export singleton instance
|
|
@@ -29,7 +29,7 @@ export class PlaygroundService {
|
|
|
29
29
|
pollingInterval = null;
|
|
30
30
|
pollingSessionId = null;
|
|
31
31
|
currentBackoff = DEFAULT_POLLING_INTERVAL;
|
|
32
|
-
|
|
32
|
+
lastSequenceNumber = null;
|
|
33
33
|
constructor() { }
|
|
34
34
|
/**
|
|
35
35
|
* Get the singleton instance of PlaygroundService
|
|
@@ -171,19 +171,18 @@ export class PlaygroundService {
|
|
|
171
171
|
* Get messages from a playground session
|
|
172
172
|
*
|
|
173
173
|
* @param sessionId - The session UUID
|
|
174
|
-
* @param
|
|
174
|
+
* @param afterSequence - Optional sequence number cursor — returns only messages with sequenceNumber > this value
|
|
175
175
|
* @param limit - Maximum number of messages to return
|
|
176
176
|
* @returns Messages and session status
|
|
177
177
|
*/
|
|
178
|
-
async getMessages(sessionId,
|
|
178
|
+
async getMessages(sessionId, afterSequence, limit) {
|
|
179
179
|
const config = this.getConfig();
|
|
180
180
|
let url = buildEndpointUrl(config, config.endpoints.playground.getMessages, {
|
|
181
181
|
sessionId
|
|
182
182
|
});
|
|
183
|
-
// Add query parameters
|
|
184
183
|
const params = new URLSearchParams();
|
|
185
|
-
if (
|
|
186
|
-
params.append('since',
|
|
184
|
+
if (afterSequence !== undefined) {
|
|
185
|
+
params.append('since', afterSequence.toString());
|
|
187
186
|
}
|
|
188
187
|
if (limit !== undefined) {
|
|
189
188
|
params.append('limit', limit.toString());
|
|
@@ -244,24 +243,27 @@ export class PlaygroundService {
|
|
|
244
243
|
* @param callback - Callback function to handle new messages
|
|
245
244
|
* @param interval - Polling interval in milliseconds (default: 1500)
|
|
246
245
|
* @param shouldStopPolling - Optional override for stop conditions (default: defaultShouldStopPolling)
|
|
246
|
+
* @param initialSequenceNumber - Optional sequence number to seed polling from (avoids re-fetching already loaded messages)
|
|
247
247
|
*/
|
|
248
|
-
startPolling(sessionId, callback, interval = DEFAULT_POLLING_INTERVAL, shouldStopPolling) {
|
|
248
|
+
startPolling(sessionId, callback, interval = DEFAULT_POLLING_INTERVAL, shouldStopPolling, initialSequenceNumber) {
|
|
249
249
|
// Stop any existing polling
|
|
250
250
|
this.stopPolling();
|
|
251
251
|
this.pollingSessionId = sessionId;
|
|
252
252
|
this.currentBackoff = interval;
|
|
253
|
-
this.
|
|
253
|
+
this.lastSequenceNumber = initialSequenceNumber ?? null;
|
|
254
254
|
const shouldStop = shouldStopPolling ?? defaultShouldStopPolling;
|
|
255
255
|
const poll = async () => {
|
|
256
256
|
if (this.pollingSessionId !== sessionId) {
|
|
257
257
|
return;
|
|
258
258
|
}
|
|
259
259
|
try {
|
|
260
|
-
const response = await this.getMessages(sessionId, this.
|
|
261
|
-
// Update last
|
|
260
|
+
const response = await this.getMessages(sessionId, this.lastSequenceNumber ?? undefined);
|
|
261
|
+
// Update last sequence number cursor
|
|
262
262
|
if (response.data && response.data.length > 0) {
|
|
263
263
|
const lastMessage = response.data[response.data.length - 1];
|
|
264
|
-
|
|
264
|
+
if (lastMessage.sequenceNumber !== undefined) {
|
|
265
|
+
this.lastSequenceNumber = lastMessage.sequenceNumber;
|
|
266
|
+
}
|
|
265
267
|
}
|
|
266
268
|
// Reset backoff on successful request
|
|
267
269
|
this.currentBackoff = interval;
|
|
@@ -295,7 +297,7 @@ export class PlaygroundService {
|
|
|
295
297
|
this.pollingInterval = null;
|
|
296
298
|
}
|
|
297
299
|
this.pollingSessionId = null;
|
|
298
|
-
this.
|
|
300
|
+
this.lastSequenceNumber = null;
|
|
299
301
|
this.currentBackoff = DEFAULT_POLLING_INTERVAL;
|
|
300
302
|
}
|
|
301
303
|
/**
|
|
@@ -314,6 +316,14 @@ export class PlaygroundService {
|
|
|
314
316
|
getPollingSessionId() {
|
|
315
317
|
return this.pollingSessionId;
|
|
316
318
|
}
|
|
319
|
+
/**
|
|
320
|
+
* Get the last sequence number used as cursor for incremental polling
|
|
321
|
+
*
|
|
322
|
+
* @returns The last sequence number, or null
|
|
323
|
+
*/
|
|
324
|
+
getLastSequenceNumber() {
|
|
325
|
+
return this.lastSequenceNumber;
|
|
326
|
+
}
|
|
317
327
|
}
|
|
318
328
|
/**
|
|
319
329
|
* Export singleton instance
|
|
@@ -37,13 +37,18 @@ export declare function getError(): string | null;
|
|
|
37
37
|
*/
|
|
38
38
|
export declare function getCurrentWorkflow(): Workflow | null;
|
|
39
39
|
/**
|
|
40
|
-
* Get the last poll
|
|
40
|
+
* Get the last poll sequence number cursor
|
|
41
41
|
*/
|
|
42
|
-
export declare function
|
|
42
|
+
export declare function getLastPollSequenceNumber(): number | null;
|
|
43
43
|
/**
|
|
44
44
|
* Get current session status
|
|
45
45
|
*/
|
|
46
46
|
export declare function getSessionStatus(): PlaygroundSessionStatus;
|
|
47
|
+
/**
|
|
48
|
+
* Whether the user can currently send a message.
|
|
49
|
+
* False when executing, when awaiting input, or when no session exists.
|
|
50
|
+
*/
|
|
51
|
+
export declare function getCanSendMessage(): boolean;
|
|
47
52
|
/**
|
|
48
53
|
* Get message count
|
|
49
54
|
*/
|
|
@@ -78,6 +83,12 @@ export declare function getSessionCount(): number;
|
|
|
78
83
|
export declare function getPinnedExecutionId(): string | null;
|
|
79
84
|
export declare function getLatestExecutionId(): string | null;
|
|
80
85
|
export declare function getActiveExecutionId(): string | null;
|
|
86
|
+
/**
|
|
87
|
+
* Counter that increments whenever new messages arrive and the pipeline display
|
|
88
|
+
* should re-fetch — i.e. when following latest or pinned to the latest execution.
|
|
89
|
+
* Pass to PipelinePanel's refreshTrigger prop.
|
|
90
|
+
*/
|
|
91
|
+
export declare function getPipelineRefreshTrigger(): number;
|
|
81
92
|
/**
|
|
82
93
|
* Playground store actions for modifying state
|
|
83
94
|
*/
|
|
@@ -143,12 +154,6 @@ export declare const playgroundActions: {
|
|
|
143
154
|
* Clear all messages
|
|
144
155
|
*/
|
|
145
156
|
clearMessages: () => void;
|
|
146
|
-
/**
|
|
147
|
-
* Set the executing state
|
|
148
|
-
*
|
|
149
|
-
* @param executing - Whether execution is in progress
|
|
150
|
-
*/
|
|
151
|
-
setExecuting: (executing: boolean) => void;
|
|
152
157
|
/**
|
|
153
158
|
* Set the loading state
|
|
154
159
|
*
|
|
@@ -166,7 +171,7 @@ export declare const playgroundActions: {
|
|
|
166
171
|
*
|
|
167
172
|
* @param timestamp - ISO 8601 timestamp
|
|
168
173
|
*/
|
|
169
|
-
|
|
174
|
+
updateLastPollSequenceNumber: (seq: number) => void;
|
|
170
175
|
/**
|
|
171
176
|
* Reset all playground state
|
|
172
177
|
*/
|
|
@@ -180,14 +185,11 @@ export declare const playgroundActions: {
|
|
|
180
185
|
pinExecution(executionId: string | null): void;
|
|
181
186
|
};
|
|
182
187
|
/**
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
* @param isTerminalStatus - Function to determine if a status clears isExecuting (default: defaultIsTerminalStatus)
|
|
188
|
-
* @returns A callback suitable for playgroundService.startPolling() or pushMessages()
|
|
188
|
+
* Apply a server response to the store. All message and status updates from
|
|
189
|
+
* the server flow through here — polling callback, manual fetches, interrupt
|
|
190
|
+
* resolution. Nothing updates messages or session status except this function.
|
|
189
191
|
*/
|
|
190
|
-
export declare function
|
|
192
|
+
export declare function applyServerResponse(response: PlaygroundMessagesApiResponse): void;
|
|
191
193
|
/**
|
|
192
194
|
* Get the current session ID
|
|
193
195
|
*
|
|
@@ -208,11 +210,11 @@ export declare function isSessionSelected(sessionId: string): boolean;
|
|
|
208
210
|
*/
|
|
209
211
|
export declare function getMessagesSnapshot(): PlaygroundMessage[];
|
|
210
212
|
/**
|
|
211
|
-
* Get the latest message
|
|
213
|
+
* Get the sequence number of the latest message, used to seed incremental polling.
|
|
212
214
|
*
|
|
213
|
-
* @returns
|
|
215
|
+
* @returns Sequence number of the last message, or null
|
|
214
216
|
*/
|
|
215
|
-
export declare function
|
|
217
|
+
export declare function getLatestSequenceNumber(): number | null;
|
|
216
218
|
/**
|
|
217
219
|
* Subscribe to session status changes using $effect.root.
|
|
218
220
|
* This is designed for use in non-component contexts (e.g., mount.ts).
|
|
@@ -228,7 +230,6 @@ export declare function subscribeToSessionStatus(callback: (status: PlaygroundSe
|
|
|
228
230
|
* has stopped but new messages may exist on the server.
|
|
229
231
|
*
|
|
230
232
|
* @param fetchMessages - Async function to fetch messages from the API
|
|
231
|
-
* @param isTerminalStatus - Optional override for terminal status check
|
|
232
233
|
* @returns Promise that resolves when messages are refreshed
|
|
233
234
|
*/
|
|
234
|
-
export declare function refreshSessionMessages(fetchMessages: (sessionId: string) => Promise<PlaygroundMessagesApiResponse
|
|
235
|
+
export declare function refreshSessionMessages(fetchMessages: (sessionId: string) => Promise<PlaygroundMessagesApiResponse>): Promise<void>;
|