@widgetic/creator 0.3.62 → 0.3.64
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/CreatorApp.svelte +60 -5
- package/dist/components/WidgetDetails.svelte +18 -2
- package/dist/components/WidgetDetails.svelte.d.ts +2 -0
- package/dist/components/WidgetShapeLivePreviews.svelte +13 -0
- package/dist/components/WidgetShapeLivePreviews.svelte.d.ts +1 -0
- package/dist/stores/websocketStore.js +26 -4
- package/dist/utils/operationStream.js +5 -1
- package/package.json +1 -1
package/dist/CreatorApp.svelte
CHANGED
|
@@ -494,7 +494,10 @@
|
|
|
494
494
|
let WidgeticCanvas: any;
|
|
495
495
|
let canvasRef: any;
|
|
496
496
|
/** HTML iframe overlays over Fabric WidgetShapes (W3). */
|
|
497
|
-
let widgetShapeLivePreviewsRef: {
|
|
497
|
+
let widgetShapeLivePreviewsRef: {
|
|
498
|
+
sync?: () => void;
|
|
499
|
+
requestReload?: (widgetId: string) => void;
|
|
500
|
+
} | null = null;
|
|
498
501
|
/** Selected WidgetShape id for chrome highlight (from Fabric selection). */
|
|
499
502
|
let canvasSelectedWidgetShapeId: string | null = null;
|
|
500
503
|
let widgetDetailsModulePromise: Promise<any> | null = null;
|
|
@@ -7087,6 +7090,13 @@
|
|
|
7087
7090
|
signal
|
|
7088
7091
|
})
|
|
7089
7092
|
.then((finalOp) => {
|
|
7093
|
+
if (!settled && !signal?.aborted && onMessage) {
|
|
7094
|
+
try {
|
|
7095
|
+
onMessage(finalOp as OperationStatus);
|
|
7096
|
+
} catch (callbackError) {
|
|
7097
|
+
console.warn('waitForOperation terminal callback error', callbackError);
|
|
7098
|
+
}
|
|
7099
|
+
}
|
|
7090
7100
|
settle('resolve', finalOp as OperationStatus);
|
|
7091
7101
|
})
|
|
7092
7102
|
.catch((streamError) => {
|
|
@@ -8223,6 +8233,11 @@ Requirements:
|
|
|
8223
8233
|
const responseData = response?.data || response;
|
|
8224
8234
|
const operationId = responseData?.operationId;
|
|
8225
8235
|
|
|
8236
|
+
// The Worker serves rebuilt code at the same preview URL, so the panel
|
|
8237
|
+
// must know when the current generation started — hasLiveWorkerPreview()
|
|
8238
|
+
// only counts frames loaded after this moment as "fresh".
|
|
8239
|
+
getWidgetDetailsRef(generationWidgetId)?.markCodegenStarted?.();
|
|
8240
|
+
|
|
8226
8241
|
if (!operationId) {
|
|
8227
8242
|
patchCodegenUiState(generationWidgetId, {
|
|
8228
8243
|
generateCodeStatus: '❌ Code generation started, but no operation ID was returned.',
|
|
@@ -8497,6 +8512,10 @@ Requirements:
|
|
|
8497
8512
|
[generationWidgetId]: 'preview',
|
|
8498
8513
|
};
|
|
8499
8514
|
syncWidgetShapeLivePreviews();
|
|
8515
|
+
// The Worker serves the rebuilt code at the SAME /preview URL — sync()
|
|
8516
|
+
// only reloads when src changes, so force a cache-busted iframe reload
|
|
8517
|
+
// on the canvas shape overlay or it keeps showing the previous version.
|
|
8518
|
+
widgetShapeLivePreviewsRef?.requestReload?.(generationWidgetId);
|
|
8500
8519
|
panelDisplayRevision += 1;
|
|
8501
8520
|
if (!completedPreviewBuildErr) {
|
|
8502
8521
|
await applyDesignSchemaDefaultsToPreview(generationWidgetId);
|
|
@@ -9311,10 +9330,43 @@ Requirements:
|
|
|
9311
9330
|
|
|
9312
9331
|
function pushCodegenProgressToUi(op: OperationStatus, widgetId: string | null | undefined) {
|
|
9313
9332
|
const line = formatCodegenProgressStatus(op);
|
|
9333
|
+
// SSE progress frames can arrive many times per second while the LLM
|
|
9334
|
+
// streams. Each one re-renders every open panel — coalesce them.
|
|
9314
9335
|
if (widgetId) {
|
|
9315
|
-
|
|
9316
|
-
}
|
|
9317
|
-
|
|
9336
|
+
scheduleCodegenStatusPush(widgetId, line);
|
|
9337
|
+
}
|
|
9338
|
+
scheduleCodegenChatStatusPush(widgetId, line);
|
|
9339
|
+
}
|
|
9340
|
+
|
|
9341
|
+
const CODEGEN_STATUS_THROTTLE_MS = 400;
|
|
9342
|
+
const codegenStatusTimers: Record<string, ReturnType<typeof setTimeout>> = {};
|
|
9343
|
+
const codegenStatusPendingLine: Record<string, string> = {};
|
|
9344
|
+
const codegenChatStatusTimers: Record<string, ReturnType<typeof setTimeout>> = {};
|
|
9345
|
+
const codegenChatStatusPendingLine: Record<string, string> = {};
|
|
9346
|
+
|
|
9347
|
+
/** At most one panel status write per widget per interval; trailing line always lands. */
|
|
9348
|
+
function scheduleCodegenStatusPush(widgetId: string, line: string): void {
|
|
9349
|
+
codegenStatusPendingLine[widgetId] = line;
|
|
9350
|
+
if (codegenStatusTimers[widgetId]) return;
|
|
9351
|
+
codegenStatusTimers[widgetId] = setTimeout(() => {
|
|
9352
|
+
delete codegenStatusTimers[widgetId];
|
|
9353
|
+
const pending = codegenStatusPendingLine[widgetId];
|
|
9354
|
+
delete codegenStatusPendingLine[widgetId];
|
|
9355
|
+
if (pending) patchCodegenUiState(widgetId, { generateCodeStatus: pending });
|
|
9356
|
+
}, CODEGEN_STATUS_THROTTLE_MS);
|
|
9357
|
+
}
|
|
9358
|
+
|
|
9359
|
+
/** Same throttle for the in-chat status bubble (updateLatestCodegenStatusMessage). */
|
|
9360
|
+
function scheduleCodegenChatStatusPush(widgetId: string | null | undefined, line: string): void {
|
|
9361
|
+
const key = widgetId || '_focused';
|
|
9362
|
+
codegenChatStatusPendingLine[key] = line;
|
|
9363
|
+
if (codegenChatStatusTimers[key]) return;
|
|
9364
|
+
codegenChatStatusTimers[key] = setTimeout(() => {
|
|
9365
|
+
delete codegenChatStatusTimers[key];
|
|
9366
|
+
const pending = codegenChatStatusPendingLine[key];
|
|
9367
|
+
delete codegenChatStatusPendingLine[key];
|
|
9368
|
+
if (pending) getChatMethodsForWidget(widgetId)?.updateLatestCodegenStatusMessage?.(pending);
|
|
9369
|
+
}, CODEGEN_STATUS_THROTTLE_MS);
|
|
9318
9370
|
}
|
|
9319
9371
|
|
|
9320
9372
|
function patchCodegenUiState(
|
|
@@ -14748,7 +14800,10 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
14748
14800
|
{@const shapeSource = shapePreviewSourceByWidgetId[panelWidgetId] || defaultVersionSource}
|
|
14749
14801
|
{@const viewingVersion = shapeSource && shapeSource.startsWith('v:')
|
|
14750
14802
|
? parseInt(shapeSource.slice(2), 10)
|
|
14751
|
-
|
|
14803
|
+
// A 'preview' source (Worker HEAD, unpublished) is not a published
|
|
14804
|
+
// version — falling back to panelPublishedVersion produced `vnull`
|
|
14805
|
+
// right after generation. The newest draft IS this HEAD's version.
|
|
14806
|
+
: (newestDraft ? newestDraft.version : panelPublishedVersion)}
|
|
14752
14807
|
{@const viewingIsLive = Number(viewingVersion) === Number(panelPublishedVersion)}
|
|
14753
14808
|
<!--
|
|
14754
14809
|
A version is a Draft until it is published. The history is one
|
|
@@ -801,7 +801,20 @@
|
|
|
801
801
|
// console.log('[WidgetDetails] Codegen preview marked fresh — skipping GitLab rebuilds for 30s');
|
|
802
802
|
}
|
|
803
803
|
|
|
804
|
-
/**
|
|
804
|
+
/** Timestamp of generation start — set by the parent when the operation begins. */
|
|
805
|
+
let codegenStartedAt: number | null = null;
|
|
806
|
+
/** Parent calls this when a generation starts for THIS widget's panel. */
|
|
807
|
+
export function markCodegenStarted(): void {
|
|
808
|
+
codegenStartedAt = Date.now();
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* True when the iframe is already showing this widget's Worker preview AND was
|
|
813
|
+
* reloaded after the current generation started. The Worker serves new code at
|
|
814
|
+
* the same `/preview/{id}` URL, so URL equality alone cannot tell a stale frame
|
|
815
|
+
* (pre-codegen) from a fresh one — that mismatch left the old version on screen
|
|
816
|
+
* after generation.
|
|
817
|
+
*/
|
|
805
818
|
export function hasLiveWorkerPreview(): boolean {
|
|
806
819
|
if (!selectedWidgetId) return false;
|
|
807
820
|
if (!iframeSrc || iframeSrc === 'about:blank') return false;
|
|
@@ -809,7 +822,10 @@
|
|
|
809
822
|
const workerUrl = dynamicWorkerPreviewUrl || buildWorkerPreviewUrl(selectedWidgetId);
|
|
810
823
|
const currentBase = normalizePreviewBaseUrl(iframeSrc);
|
|
811
824
|
const workerBase = normalizePreviewBaseUrl(toPreviewFetchUrl(workerUrl));
|
|
812
|
-
|
|
825
|
+
if (!currentBase || !workerBase || currentBase !== workerBase) return false;
|
|
826
|
+
// During/after a generation, only a frame (re)loaded after it started counts.
|
|
827
|
+
if (codegenStartedAt !== null && lastPreviewIframeLoadAt < codegenStartedAt) return false;
|
|
828
|
+
return true;
|
|
813
829
|
}
|
|
814
830
|
|
|
815
831
|
// Preview resize
|
|
@@ -108,6 +108,7 @@ declare const WidgetDetails: $$__sveltets_2_IsomorphicComponent<{
|
|
|
108
108
|
showPublishedArtifact?: (url: string | null) => void;
|
|
109
109
|
finishCompositionHydration?: () => void;
|
|
110
110
|
markCodegenPreviewFresh?: () => void;
|
|
111
|
+
markCodegenStarted?: () => void;
|
|
111
112
|
hasLiveWorkerPreview?: () => boolean;
|
|
112
113
|
getLatestRuntimeLogs?: (maxLines?: number) => string[];
|
|
113
114
|
resumeDeferredPreviewLoad?: () => Promise<void>;
|
|
@@ -204,6 +205,7 @@ declare const WidgetDetails: $$__sveltets_2_IsomorphicComponent<{
|
|
|
204
205
|
showPublishedArtifact: (url: string | null) => void;
|
|
205
206
|
finishCompositionHydration: () => void;
|
|
206
207
|
markCodegenPreviewFresh: () => void;
|
|
208
|
+
markCodegenStarted: () => void;
|
|
207
209
|
hasLiveWorkerPreview: () => boolean;
|
|
208
210
|
getLatestRuntimeLogs: (maxLines?: number) => string[];
|
|
209
211
|
resumeDeferredPreviewLoad: () => Promise<void>;
|
|
@@ -171,6 +171,19 @@
|
|
|
171
171
|
}
|
|
172
172
|
pendingRefreshTokens.delete(widgetId);
|
|
173
173
|
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Parent-driven forced reload for widgets whose Worker preview content changed
|
|
177
|
+
* at the same URL (codegen rebuild). `sync()` only reloads when `src` changes,
|
|
178
|
+
* so without a cache-bust token the overlay keeps showing pre-codegen content.
|
|
179
|
+
* Bumps the reload token → `{#key}` remounts the iframe with a fresh `_t`.
|
|
180
|
+
*/
|
|
181
|
+
export function requestReload(widgetId: string): void {
|
|
182
|
+
const token = Math.max(Date.now(), (reloadTokens[widgetId] || 0) + 1);
|
|
183
|
+
reloadTokens = { ...reloadTokens, [widgetId]: token };
|
|
184
|
+
iframeLoadedByWidgetId = { ...iframeLoadedByWidgetId, [widgetId]: false };
|
|
185
|
+
}
|
|
186
|
+
|
|
174
187
|
/** Which shape chrome version menu is open. */
|
|
175
188
|
let openVersionMenuWidgetId = $state<string | null>(null);
|
|
176
189
|
/** Hide Fabric placeholder flash — show loader until iframe fires load. */
|
|
@@ -90,6 +90,7 @@ interface Props {
|
|
|
90
90
|
resolvePublishDisabledReason?: ((widgetId: string, version: number) => string | null) | null;
|
|
91
91
|
}
|
|
92
92
|
declare const WidgetShapeLivePreviews: import("svelte").Component<Props, {
|
|
93
|
+
requestReload: (widgetId: string) => void;
|
|
93
94
|
sync: () => void;
|
|
94
95
|
}, "">;
|
|
95
96
|
type WidgetShapeLivePreviews = ReturnType<typeof WidgetShapeLivePreviews>;
|
|
@@ -39,6 +39,30 @@ export const codeGenerationStatus = writable({
|
|
|
39
39
|
timestamp: null
|
|
40
40
|
});
|
|
41
41
|
export const agentHarnessOutput = writable(null);
|
|
42
|
+
/**
|
|
43
|
+
* Throttle for high-frequency agent harness frames. The stream used to emit one
|
|
44
|
+
* WS event per LLM token (plus one SSE progress frame every 10 tokens), and each
|
|
45
|
+
* frame re-rendered every open panel — with `structuredClone` on schemas per
|
|
46
|
+
* render. That saturated the browser main thread and froze the whole site during
|
|
47
|
+
* generation. Frames now coalesce into one store write per interval; the final
|
|
48
|
+
* frame always flushes so the last line is never lost.
|
|
49
|
+
*/
|
|
50
|
+
const AGENT_HARNESS_FLUSH_MS = 300;
|
|
51
|
+
let agentHarnessPending = null;
|
|
52
|
+
let agentHarnessFlushTimer = null;
|
|
53
|
+
function flushAgentHarnessOutput() {
|
|
54
|
+
agentHarnessFlushTimer = null;
|
|
55
|
+
if (!agentHarnessPending)
|
|
56
|
+
return;
|
|
57
|
+
agentHarnessOutput.set(agentHarnessPending);
|
|
58
|
+
agentHarnessPending = null;
|
|
59
|
+
}
|
|
60
|
+
function pushAgentHarnessOutput(output) {
|
|
61
|
+
agentHarnessPending = { output, timestamp: new Date().toISOString() };
|
|
62
|
+
if (!agentHarnessFlushTimer) {
|
|
63
|
+
agentHarnessFlushTimer = setTimeout(flushAgentHarnessOutput, AGENT_HARNESS_FLUSH_MS);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
42
66
|
// =============================================================================
|
|
43
67
|
// Socket.IO Connection
|
|
44
68
|
// =============================================================================
|
|
@@ -204,10 +228,8 @@ function setupSocketListeners(sock) {
|
|
|
204
228
|
return;
|
|
205
229
|
if (showLogs)
|
|
206
230
|
console.log('[WsStore] Agent harness output:', output.slice(0, 120));
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
timestamp: new Date().toISOString()
|
|
210
|
-
});
|
|
231
|
+
// Coalesced write — see AGENT_HARNESS_FLUSH_MS above.
|
|
232
|
+
pushAgentHarnessOutput(output);
|
|
211
233
|
});
|
|
212
234
|
sock.on('dynamic_worker_preview_ready', (data) => {
|
|
213
235
|
if (showLogs)
|
|
@@ -84,12 +84,12 @@ export function streamOperationUpdates(operationId, options) {
|
|
|
84
84
|
reader = response.body.getReader();
|
|
85
85
|
const decoder = new TextDecoder();
|
|
86
86
|
let buffer = '';
|
|
87
|
+
// Start idle timer before any operation payload arrives.
|
|
87
88
|
armWatchdog();
|
|
88
89
|
while (true) {
|
|
89
90
|
const { done, value } = await reader.read();
|
|
90
91
|
if (done)
|
|
91
92
|
break;
|
|
92
|
-
armWatchdog();
|
|
93
93
|
buffer += decoder.decode(value, { stream: true });
|
|
94
94
|
// SSE frames are `data: {...}\n\n`; keepalive comment frames carry
|
|
95
95
|
// no data line and are skipped.
|
|
@@ -107,6 +107,10 @@ export function streamOperationUpdates(operationId, options) {
|
|
|
107
107
|
continue;
|
|
108
108
|
try {
|
|
109
109
|
const op = JSON.parse(dataLine);
|
|
110
|
+
// Only reset idle timer on real operation payloads — SSE
|
|
111
|
+
// `: keepalive` comment frames must not suppress polling fallback
|
|
112
|
+
// when the terminal `completed` event is lost behind a proxy.
|
|
113
|
+
armWatchdog();
|
|
110
114
|
onMessage?.(op);
|
|
111
115
|
const status = String(op?.status || '').toLowerCase();
|
|
112
116
|
if (TERMINAL_STATUSES.has(status)) {
|